Compare commits

...
2 Commits
Author SHA1 Message Date
Hoan Luu Huu d86d03c792 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
2023-11-27 08:26:41 -05:00
Hoan Luu Huu b93e220fef let realtimedb-helper build configuration from env vars (#109) 2023-11-14 09:12:48 -05:00
5 changed files with 83 additions and 45 deletions
+1 -28
View File
@@ -14,30 +14,6 @@ assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR env var');
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
const JAMBONES_REDIS_SENTINELS = process.env.JAMBONES_REDIS_SENTINELS ? {
sentinels: process.env.JAMBONES_REDIS_SENTINELS.split(',').map((sentinel) => {
let host, port = 26379;
if (sentinel.includes(':')) {
const arr = sentinel.split(':');
host = arr[0];
port = parseInt(arr[1], 10);
} else {
host = sentinel;
}
return {host, port};
}),
name: process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
...(process.env.JAMBONES_REDIS_SENTINEL_PASSWORD && {
password: process.env.JAMBONES_REDIS_SENTINEL_PASSWORD
}),
...(process.env.JAMBONES_REDIS_SENTINEL_USERNAME && {
username: process.env.JAMBONES_REDIS_SENTINEL_USERNAME
}),
...(process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD && {
sentinelPassword: process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD
}),
} : null;
const Srf = require('drachtio-srf');
const srf = new Srf('sbc-outbound');
const CIDRMatcher = require('cidr-matcher');
@@ -91,10 +67,7 @@ const {
decrKey,
retrieveSet,
isMemberOfSet,
} = require('@jambonz/realtimedb-helpers')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
} = require('@jambonz/realtimedb-helpers')({}, logger);
const activeCallIds = new Map();
const Emitter = require('events');
+44 -8
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) {
+30 -1
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
};
+7 -7
View File
@@ -12,7 +12,7 @@
"@jambonz/db-helpers": "^0.9.0",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "0.2.4",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/realtimedb-helpers": "^0.8.7",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.6",
"@jambonz/stats-collector": "^0.1.9",
@@ -754,9 +754,9 @@
}
},
"node_modules/@jambonz/realtimedb-helpers": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
"integrity": "sha512-ODKVVat2VAQheY13DjiAxiBBOmffMoeu3xviTjsTODr3ySZkqQZvSWU7N80pAIZvoiWG1UUEy9yI8D8mE4Jgiw==",
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.7.tgz",
"integrity": "sha512-gDq56YUd/SIx8Q9mJ+WEIfLCUcGNQ33A0iWKhhwMFwzqTESlhHjy97oQGr7BZVQGB8c0ZnqKIxuAv4ruI3ew7w==",
"dependencies": {
"debug": "^4.3.4",
"ioredis": "^5.3.2"
@@ -5534,9 +5534,9 @@
}
},
"@jambonz/realtimedb-helpers": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
"integrity": "sha512-ODKVVat2VAQheY13DjiAxiBBOmffMoeu3xviTjsTODr3ySZkqQZvSWU7N80pAIZvoiWG1UUEy9yI8D8mE4Jgiw==",
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.7.tgz",
"integrity": "sha512-gDq56YUd/SIx8Q9mJ+WEIfLCUcGNQ33A0iWKhhwMFwzqTESlhHjy97oQGr7BZVQGB8c0ZnqKIxuAv4ruI3ew7w==",
"requires": {
"debug": "^4.3.4",
"ioredis": "^5.3.2"
+1 -1
View File
@@ -28,7 +28,7 @@
},
"dependencies": {
"@jambonz/db-helpers": "^0.9.0",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/realtimedb-helpers": "^0.8.7",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "0.2.4",
"@jambonz/rtpengine-utils": "^0.4.3",