feat support invite private to private network (#111)

* feat support invite private to private network

* feat support invite private to private network

* support multi private cidrs

* fix review comment

* fix matching regex

* fix regex to extract IP from sip uri
This commit is contained in:
Hoan Luu Huu
2023-11-27 20:26:41 +07:00
committed by GitHub
parent b93e220fef
commit d86d03c792
2 changed files with 74 additions and 9 deletions

View File

@@ -1,7 +1,7 @@
const Emitter = require('events');
const sdpTransform = require('sdp-transform');
const SrsClient = require('@jambonz/siprec-client-utils');
const {makeRtpEngineOpts, nudgeCallCounts} = require('./utils');
const {makeRtpEngineOpts, nudgeCallCounts, isPrivateVoipNetwork} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
const {SipError, stringifyUri, parseUri} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-outbound');
@@ -204,13 +204,19 @@ class CallSession extends Emitter {
destUri = stringifyUri(uri);
this.logger.info(`overriding destination user with ${dest}, so final uri is ${destUri}`);
}
uris = [destUri];
uris = [{
private_network: contact.includes('transport=ws') ? false : await isPrivateVoipNetwork(destUri),
uri: destUri
}];
if (!contact.includes('transport=ws')) {
proxy = this.req.locals.registration.proxy;
}
}
else if (this.req.locals.target === 'forward') {
uris = [this.req.uri];
uris = [{
private_network: await isPrivateVoipNetwork(this.req.uri),
uri: this.req.uri
}];
if (this.req.has('X-SIP-Proxy')) {
proxy = this.req.get('X-SIP-Proxy');
if (!proxy.startsWith('sip:') && !proxy.startsWith('sips:')) proxy = `sip:${proxy}`;
@@ -220,9 +226,15 @@ class CallSession extends Emitter {
const vmailParam = 'opaque=app:voicemail';
proxy = `sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com:5061;transport=tls`;
if (this.req.uri.includes(vmailParam)) {
uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com;${vmailParam}`];
uris = [{
private_network: false,
uri: `sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com;${vmailParam}`
}];
}
else uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com`];
else uris = [{
private_network: false,
uri: `sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com`
}];
headers = {
...headers,
Contact: `sip:${this.req.calledNumber}@${this.req.get('X-MS-Teams-Tenant-FQDN')}:5061;transport=tls`
@@ -301,6 +313,13 @@ class CallSession extends Emitter {
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, true, o.pad_crypto, true);
}
});
// Check private network for each gw
uris = await Promise.all(uris.map(async(u) => {
return {
private_network: await isPrivateVoipNetwork(u),
uri: u
};
}));
this.logger.debug({uris, voip_carrier_sid}, 'selected outbound gateways for requested carrier');
}
else {
@@ -315,15 +334,22 @@ class CallSession extends Emitter {
debug(`sending call to PSTN ${uris}`);
}
// private_network should be called at last
uris = uris.sort((a, b) => a.private_network - b.private_network);
const toPrivate = uris.some((u) => u.private_network === true);
const toPublic = uris.some((u) => u.private_network === false);
let isOfferUpdatedToPrivate = toPrivate && !toPublic;
// rtpengine 'offer'
const opts = updateRtpEngineFlags(this.req.body, {
...this.rtpEngineOpts.common,
...this.rtpEngineOpts.uac.mediaOpts,
'from-tag': this.rtpEngineOpts.uas.tag,
direction: ['private', 'public'],
direction: ['private', toPublic ? 'public' : 'private'],
sdp: this.req.body
});
const response = await this.offer(opts);
let response = await this.offer(opts);
debug(`response from rtpengine to offer ${JSON.stringify(response)}`);
this.logger.debug({offer: opts, response}, 'initial offer to rtpengine');
if ('ok' !== response.result) {
@@ -338,7 +364,17 @@ class CallSession extends Emitter {
let earlyMedia = false;
while (uris.length) {
let hdrs = { ...headers};
const uri = uris.shift();
const {private_network, uri} = uris.shift();
if (private_network && !isOfferUpdatedToPrivate) {
// Cannot make call to all public Uris, now come to talk with private network Uris
this.rtpEngineResource.destroy()
.catch((err) => this.logger.info({err}, 'Error destroying rtpe to re-connect to private network'));
response = await this.offer({
...opts,
direction: ['private', 'private']
});
isOfferUpdatedToPrivate = true;
}
const gw = mapGateways.get(uri);
const passFailure = 0 === uris.length; // only a single target
if (0 === uris.length) {

View File

@@ -1,6 +1,11 @@
const rtpCharacteristics = require('../data/rtp-transcoding');
const srtpCharacteristics = require('../data/srtp-transcoding');
const debug = require('debug')('jambonz:sbc-outbound');
const CIDRMatcher = require('cidr-matcher');
const dns = require('dns');
const cidrMatcher = process.env.PRIVATE_VOIP_NETWORK_CIDR ?
new CIDRMatcher(process.env.PRIVATE_VOIP_NETWORK_CIDR.split(',')) : null;
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams) {
const from = req.getParsedHeader('from');
@@ -181,6 +186,29 @@ const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
return {callsSP: null, calls: null, callsApp: null};
};
const isPrivateVoipNetwork = async(uri) => {
if (cidrMatcher) {
try {
const arr = /sips?:.*@(.*?)(:\d+)?(;.*)$/.exec(uri);
if (arr) {
const input = arr[1];
let addresses;
if (input.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
addresses = [input];
} else {
addresses = await dns.resolve4(input);
}
for (const ip of addresses) {
if (cidrMatcher.contains(ip)) {
return true;
}
}
}
} catch (err) {}
}
return false;
};
module.exports = {
makeRtpEngineOpts,
selectHostPort,
@@ -190,5 +218,6 @@ module.exports = {
equalsIgnoreOrder,
systemHealth,
createHealthCheckApp,
nudgeCallCounts
nudgeCallCounts,
isPrivateVoipNetwork
};