add support for registration trunks which result in a set of ephemera… (#112)

* add support for registration trunks which result in a set of ephemeral sip gateways to be stored in redis

* wip

* refactor createEphemeralGateways into realtime dbhelpers

* minor

* update eslint
This commit is contained in:
Dave Horton
2025-10-21 07:32:06 -04:00
committed by GitHub
parent d08496840e
commit e94ae431d8
15 changed files with 697 additions and 783 deletions

View File

@@ -1,3 +1,4 @@
const dns = require('dns').promises;
const {
JAMBONES_REGBOT_DEFAULT_EXPIRES_INTERVAL,
JAMBONES_REGBOT_MIN_EXPIRES_INTERVAL,
@@ -17,24 +18,20 @@ class Regbot {
this.logger = logger;
[
'voip_carrier_sid',
'ipv4',
'port',
'username',
'password',
'sip_realm',
'protocol',
'account_sip_realm'
'account_sip_realm',
'outbound_sip_proxy',
'trunk_type'
].forEach((prop) => this[prop] = opts[prop]);
this.voip_carrier_sid = opts.voip_carrier_sid;
this.username = opts.username;
this.password = opts.password;
this.sip_realm = opts.sip_realm || opts.ipv4;
this.ipv4 = opts.ipv4;
this.port = opts.port;
this.use_public_ip_in_contact = opts.use_public_ip_in_contact || JAMBONES_REGBOT_CONTACT_USE_IP;
this.use_sips_scheme = opts.use_sips_scheme || false;
this.outbound_sip_proxy = opts.outbound_sip_proxy;
this.fromUser = opts.from_user || this.username;
const fromDomain = opts.from_domain || this.sip_realm;
@@ -72,6 +69,7 @@ class Regbot {
}
async register(srf) {
const { createEphemeralGateway } = srf.locals.realtimeDbHelpers;
const { updateVoipCarriersRegisterStatus } = srf.locals.dbHelpers;
const { writeAlerts, localSIPDomain } = srf.locals;
try {
@@ -183,6 +181,8 @@ class Regbot {
this.timer = setTimeout(this.register.bind(this, srf), (expires / 2) * 1000);
}
const timestamp = new Date().toISOString();
//update registration status for the carrier in the database
updateVoipCarriersRegisterStatus(this.voip_carrier_sid, JSON.stringify({
status: res.status === 200 ? 'ok' : 'fail',
reason: `${res.status} ${res.reason}`,
@@ -191,6 +191,31 @@ class Regbot {
timestamp: timestamp,
expires: expires
}));
// for reg trunks, create ephemeral set of IP addresses for inbound gateways
if (this.trunk_type === 'reg') {
const addresses = [];
if (this.port) {
const addrs = await dnsResolverA(this.logger, this.sip_realm);
addresses.push(...addrs);
}
else {
const addrs = await dnsResolverSrv(this.logger, this.sip_realm, this.transport);
addresses.push(...addrs);
}
if (addresses.length) {
try {
await Promise.all(
addresses.map((ip) => createEphemeralGateway(ip, this.voip_carrier_sid, expires))
);
} catch (err) {
this.logger.error({addresses, err}, 'Error creating hash for reg-gateway');
}
this.logger.debug({addresses},
`Created ephemeral gateways for registration trunk ${this.voip_carrier_sid}, ${this.sip_realm}`);
}
}
});
} catch (err) {
this.logger.error({ err }, `${this.aor}: Error registering to ${this.ipv4}:${this.port}`);
@@ -204,5 +229,44 @@ class Regbot {
}
}
const dnsResolverA = async(logger, hostname) => {
try {
const addresses = await dns.resolve4(hostname);
logger.debug({addresses}, `Regbot: resolved ${hostname} into ${addresses.length} IPs`);
return addresses;
} catch (err) {
logger.info({err}, `Error resolving ${hostname}`);
}
return [];
};
const dnsResolverSrv = async(logger, hostname, transport) => {
let name;
switch (transport) {
case 'tls':
name = `_sips._tcp.${hostname}`;
break;
case 'tcp':
name = `_sip._tcp.${hostname}`;
break;
default:
name = `_sip._udp.${hostname}`;
}
try {
const arr = await dns.resolveSrv(name);
logger.debug({arr}, `Regbot: resolved ${hostname}/${transport} into ${arr.length} results`);
const ips = await Promise.all(
arr.map((obj) => dnsResolverA(logger, obj.name))
);
return ips.flat();
}
catch (err) {
logger.info({err}, `SRV Error resolving ${hostname}`);
}
return [];
};
module.exports = Regbot;

View File

@@ -18,6 +18,7 @@ const regbots = [];
const carriers = [];
const gateways = [];
const getCountSuccessfulRegbots = () => regbots.filter((rb) => rb.status === 'registered').length;
function pickRelevantCarrierProperties(c) {
return {
@@ -32,6 +33,7 @@ function pickRelevantCarrierProperties(c) {
register_public_ip_in_contact: c.register_public_ip_in_contact,
outbound_sip_proxy: c.outbound_sip_proxy,
account_sid: c.account_sid,
trunk_type: c.trunk_type || 'static_ip'
};
}
@@ -91,6 +93,14 @@ module.exports = async(logger, srf) => {
active: false
};
srf.locals.regbotStatus = () => {
return {
total: regbots.length,
registered: getCountSuccessfulRegbots(),
active: srf.locals.regbot.active
};
};
/* Set the Local SIP domain on srf.locals */
await getLocalSIPDomain(logger, srf); // Initial Setup
setInterval(getLocalSIPDomain, 300000, logger, srf); //Refresh SIP Domain every 5 mins
@@ -209,11 +219,10 @@ const updateCarrierRegbots = async(logger, srf) => {
}
}
if (JSON.stringify(gws) !== JSON.stringify(gateways)) hasChanged = true;
if (hasChanged) {
debug('updateCarrierRegbots: got new or changed carriers');
logger.info('updateCarrierRegbots: got new or changed carriers');
logger.info({gws}, 'updateCarrierRegbots: got new or changed carriers');
carriers.length = 0;
Array.prototype.push.apply(carriers, cs);
@@ -248,7 +257,8 @@ const updateCarrierRegbots = async(logger, srf) => {
from_user: gw.carrier.register_from_user,
from_domain: gw.carrier.register_from_domain,
use_public_ip_in_contact: gw.carrier.register_public_ip_in_contact,
outbound_sip_proxy: gw.carrier.outbound_sip_proxy
outbound_sip_proxy: gw.carrier.outbound_sip_proxy,
trunk_type: gw.carrier.trunk_type
});
regbots.push(rb);
rb.start(srf);