mirror of
https://github.com/jambonz/sbc-sip-sidecar.git
synced 2025-12-18 20:17:47 +00:00
275 lines
8.1 KiB
JavaScript
275 lines
8.1 KiB
JavaScript
const assert = require('assert');
|
|
const {
|
|
JAMBONES_MYSQL_HOST,
|
|
JAMBONES_MYSQL_USER,
|
|
JAMBONES_MYSQL_PASSWORD,
|
|
JAMBONES_MYSQL_DATABASE,
|
|
JAMBONES_REDIS_SENTINEL_MASTER_NAME,
|
|
JAMBONES_REDIS_SENTINELS,
|
|
JAMBONES_REDIS_HOST,
|
|
DRACHTIO_HOST,
|
|
DRACHTIO_PORT,
|
|
DRACHTIO_SECRET,
|
|
JAMBONES_TIME_SERIES_HOST,
|
|
JAMBONES_LOGLEVEL,
|
|
JAMBONES_MYSQL_PORT,
|
|
JAMBONES_MYSQL_CONNECTION_LIMIT,
|
|
NODE_ENV,
|
|
SBC_PUBLIC_ADDRESS_KEEP_ALIVE_IN_MILISECOND
|
|
} = require('./lib/config');
|
|
|
|
assert.ok(JAMBONES_MYSQL_HOST &&
|
|
JAMBONES_MYSQL_USER &&
|
|
JAMBONES_MYSQL_PASSWORD &&
|
|
JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
|
if (JAMBONES_REDIS_SENTINELS) {
|
|
assert.ok(JAMBONES_REDIS_SENTINEL_MASTER_NAME,
|
|
'missing JAMBONES_REDIS_SENTINEL_MASTER_NAME env var, JAMBONES_REDIS_SENTINEL_PASSWORD env var is optional');
|
|
} else {
|
|
assert.ok(JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
|
}
|
|
assert.ok(DRACHTIO_HOST, 'missing DRACHTIO_HOST env var');
|
|
assert.ok(DRACHTIO_PORT, 'missing DRACHTIO_PORT env var');
|
|
assert.ok(DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
|
assert.ok(JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
|
|
|
|
const CIDRMatcher = require('cidr-matcher');
|
|
const logger = require('pino')({ level: JAMBONES_LOGLEVEL || 'info' });
|
|
const Srf = require('drachtio-srf');
|
|
const srf = new Srf();
|
|
const StatsCollector = require('@jambonz/stats-collector');
|
|
const stats = new StatsCollector(logger);
|
|
const { initLocals, rejectIpv4, checkCache, checkAccountLimits } = require('./lib/middleware');
|
|
const responseTime = require('drachtio-mw-response-time');
|
|
const regParser = require('drachtio-mw-registration-parser');
|
|
const Registrar = require('@jambonz/mw-registrar');
|
|
const digestChallenge = require('@jambonz/digest-utils');
|
|
const debug = require('debug')('jambonz:sbc-registrar');
|
|
const {
|
|
lookupAuthHook,
|
|
lookupAllVoipCarriers,
|
|
lookupSipGatewaysByCarrier,
|
|
lookupAccountBySipRealm,
|
|
lookupAccountCapacitiesBySid,
|
|
addSbcAddress,
|
|
cleanSbcAddresses,
|
|
updateVoipCarriersRegisterStatus,
|
|
lookupClientByAccountAndUsername,
|
|
lookupSipGatewaysByFilters,
|
|
updateSipGatewayBySid,
|
|
lookupCarrierBySid,
|
|
lookupSystemInformation,
|
|
updateCarrierBySid,
|
|
lookupAccountBySid
|
|
} = require('@jambonz/db-helpers')({
|
|
host: JAMBONES_MYSQL_HOST,
|
|
user: JAMBONES_MYSQL_USER,
|
|
port: JAMBONES_MYSQL_PORT || 3306,
|
|
password: JAMBONES_MYSQL_PASSWORD,
|
|
database: JAMBONES_MYSQL_DATABASE,
|
|
connectionLimit: JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
|
}, logger);
|
|
const {
|
|
writeAlerts,
|
|
AlertType
|
|
} = require('@jambonz/time-series')(logger, {
|
|
host: JAMBONES_TIME_SERIES_HOST,
|
|
commitSize: 50,
|
|
commitInterval: 'test' === NODE_ENV ? 7 : 20
|
|
});
|
|
|
|
const {
|
|
client,
|
|
addKey,
|
|
addKeyNx,
|
|
retrieveKey,
|
|
addToSet,
|
|
removeFromSet,
|
|
isMemberOfSet,
|
|
retrieveSet,
|
|
createEphemeralGateway,
|
|
deleteEphemeralGateway
|
|
} = require('@jambonz/realtimedb-helpers')({}, logger);
|
|
|
|
const interval = SBC_PUBLIC_ADDRESS_KEEP_ALIVE_IN_MILISECOND || 900000; // Default 15 minutes
|
|
|
|
srf.locals = {
|
|
...srf.locals,
|
|
logger,
|
|
stats,
|
|
addToSet, removeFromSet, isMemberOfSet, retrieveSet,
|
|
registrar: new Registrar(logger, client),
|
|
dbHelpers: {
|
|
lookupAccountBySid,
|
|
lookupAuthHook,
|
|
lookupAllVoipCarriers,
|
|
lookupSipGatewaysByCarrier,
|
|
lookupAccountBySipRealm,
|
|
lookupAccountCapacitiesBySid,
|
|
updateVoipCarriersRegisterStatus,
|
|
lookupClientByAccountAndUsername,
|
|
lookupSipGatewaysByFilters,
|
|
updateSipGatewayBySid,
|
|
lookupCarrierBySid,
|
|
lookupSystemInformation,
|
|
updateCarrierBySid
|
|
},
|
|
realtimeDbHelpers: {
|
|
client,
|
|
addKey,
|
|
addKeyNx,
|
|
retrieveKey,
|
|
retrieveSet,
|
|
createEphemeralGateway,
|
|
deleteEphemeralGateway
|
|
},
|
|
writeAlerts,
|
|
AlertType
|
|
};
|
|
const cidrsEnv = process.env.JAMBONES_NETWORK_CIDR || '192.168.0.0/24,172.16.0.0/16,10.0.0.0/8';
|
|
const cidrs = cidrsEnv
|
|
.split(',')
|
|
.map((s) => s.trim());
|
|
const matcher = new CIDRMatcher(cidrs);
|
|
|
|
srf.connect({ host: DRACHTIO_HOST, port: DRACHTIO_PORT, secret: DRACHTIO_SECRET });
|
|
srf.on('connect', (err, hp, version, localHostports) => {
|
|
if (err) return logger.error({ err }, 'Error connecting to drachtio server');
|
|
logger.info(`connected to drachtio listening on ${hp}, local hostports: ${localHostports}`);
|
|
|
|
if (localHostports) {
|
|
const locals = localHostports.split(',');
|
|
for (const hp of locals) {
|
|
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
|
if (arr && 'tcp' === arr[1] && matcher.contains(arr[2])) {
|
|
const hostport = `${arr[2]}:${arr[3]}`;
|
|
srf.locals.privateSipAddress = hostport;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add SBC Public IP to Database
|
|
srf.locals.sbcPublicIpAddress = {};
|
|
let defaultIp;
|
|
const map = new Map();
|
|
const hostports = hp.split(',');
|
|
for (const hp of hostports) {
|
|
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
|
if (arr) {
|
|
const ipv4 = arr[2];
|
|
const port = arr[3];
|
|
const addr = map.get(ipv4) || {ipv4};
|
|
switch (arr[1]) {
|
|
case 'udp':
|
|
srf.locals.sbcPublicIpAddress = {
|
|
...srf.locals.sbcPublicIpAddress,
|
|
udp: `${ipv4}:${port}`
|
|
};
|
|
map.set(ipv4, {...addr, port: port});
|
|
defaultIp = ipv4;
|
|
break;
|
|
case 'tls':
|
|
map.set(ipv4, {...addr, tls_port: port});
|
|
srf.locals.sbcPublicIpAddress = {
|
|
...srf.locals.sbcPublicIpAddress,
|
|
tls: `${ipv4}:${port}`
|
|
};
|
|
break;
|
|
case 'wss':
|
|
srf.locals.sbcPublicIpAddress = {
|
|
...srf.locals.sbcPublicIpAddress,
|
|
wss: `${ipv4}:${port}`
|
|
};
|
|
map.set(ipv4, {...addr, wss_port: port});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if drachtio server does not tell us the tls ip and port default to standard 5061
|
|
if (!srf.locals.sbcPublicIpAddress.tls) {
|
|
srf.locals.sbcPublicIpAddress.tls = `${defaultIp}:5061`;
|
|
}
|
|
|
|
logger.info({sbcPublicIpAddress: srf.locals.sbcPublicIpAddress}, 'sbc public ip addresses');
|
|
|
|
// Function to check if the IP address is in a private subnet (RFC 1918)
|
|
const isPrivateSubnet = (ip) => {
|
|
const [firstOctet, secondOctet] = ip.split('.').map(Number);
|
|
return (
|
|
(firstOctet === 10) || // 10.0.0.0/8
|
|
(firstOctet === 172 && secondOctet >= 16 && secondOctet <= 31) || // 172.16.0.0/12
|
|
(firstOctet === 192 && secondOctet === 168) // 192.168.0.0/16
|
|
);
|
|
};
|
|
|
|
logger.info({ips: [...map.entries()]}, 'drachtio sip contacts');
|
|
const mapOfPublicAddresses = map.size === 0 ? map : new Map(Array.from(
|
|
map.entries())
|
|
.filter(([key, value]) => !isPrivateSubnet(key)));
|
|
|
|
logger.info({ips: [...mapOfPublicAddresses.entries()]}, 'drachtio sip public contacts');
|
|
|
|
mapOfPublicAddresses.forEach((addr) => {
|
|
addSbcAddress(addr.ipv4, addr.port, addr.tls_port, addr.wss_port);
|
|
// keep alive for this SBC
|
|
setTimeout(() => {
|
|
addSbcAddress(addr.ipv4, addr.port, addr.tls_port, addr.wss_port);
|
|
}, interval);
|
|
});
|
|
|
|
// first start up, clean sbc address
|
|
cleanSbcAddresses();
|
|
setTimeout(() => {
|
|
cleanSbcAddresses();
|
|
}, interval);
|
|
|
|
/* start regbot */
|
|
require('./lib/sip-trunk-register')(logger, srf);
|
|
// Start Options bot
|
|
require('./lib/sip-trunk-options-ping')(logger, srf);
|
|
});
|
|
|
|
if (NODE_ENV === 'test') {
|
|
srf.on('error', (err) => {
|
|
logger.info(err, 'Error connecting to drachtio');
|
|
});
|
|
}
|
|
|
|
const rttMetric = (req, res, time) => {
|
|
if (res.cached) {
|
|
stats.histogram('sbc.registration.cached.response_time', time.toFixed(0), [`status:${res.statusCode}`]);
|
|
}
|
|
else {
|
|
stats.histogram('sbc.registration.total.response_time', time.toFixed(0), [`status:${res.statusCode}`]);
|
|
}
|
|
};
|
|
|
|
// middleware
|
|
srf.use('register', [
|
|
initLocals,
|
|
responseTime(rttMetric),
|
|
rejectIpv4,
|
|
regParser,
|
|
checkCache,
|
|
checkAccountLimits,
|
|
digestChallenge]);
|
|
|
|
srf.use('options', [
|
|
initLocals
|
|
]);
|
|
|
|
srf.register(require('./lib/register')({logger}));
|
|
srf.options(require('./lib/options')({srf, logger}));
|
|
|
|
// Start CLI runtime config server with access to srf.locals
|
|
require('./lib/cli/runtime-config').initialize(srf.locals, logger);
|
|
|
|
setInterval(async() => {
|
|
const count = await srf.locals.registrar.getCountOfUsers();
|
|
debug(`count of registered users: ${count}`);
|
|
stats.gauge('sbc.users.count', parseInt(count));
|
|
}, 30000);
|
|
|
|
module.exports = { srf, logger };
|