Compare commits

...
19 Commits
Author SHA1 Message Date
Dave Horton 2747bc15d6 update deps, comment out questionable code 2024-01-17 13:37:07 -05:00
Dave Horton cdfbf384fe update db-helpers 2024-01-17 13:26:37 -05:00
Hoan Luu Huu 96a83619c7 fix sbc to forward BYE header (#129)
* fix sbc to forward BYE header

* fix sbc to forward BYE header
2023-12-28 20:45:38 -05:00
Hoan Luu Huu b8a67019a1 support sending DTMF to caller (#125)
* support sending DTMF to caller

* wip
2023-12-13 09:14:27 -05:00
Hoan Luu Huu 0e1627ba82 phone number's application should override voice carrier default application (#128)
* phone number's application should override voice carrier default application

* fix review comments

* fix review comments
2023-12-13 08:25:26 -05:00
Hoan Luu Huu bb857a72ff let realtimedb-utils build configuration from env vars (#122) 2023-11-14 09:13:08 -05:00
Hoan Luu Huu aea3033ae0 support PRIVATE_VOIP_NETWORK_CIDR for SDP negotiation (#124)
* support PRIVATE_VOIP_NETWORK_CIDR for SDP negotiation

* support multi PRIVATE_VOIP_NETWORK_CIDR
2023-11-13 15:24:41 -05:00
Dave Horton 9b9eaa37fb 0.8.5 2023-11-09 12:39:24 -05:00
Dave Horton 56e0e3c0f2 update drachtio-srf 2023-11-09 10:02:46 -05:00
Hoan Luu Huu 6a6b4dd26f upgrade drachtio srf (#121)
* upgrade drachtio srf

* fix review comments
2023-10-24 08:42:53 -04:00
Hoan Luu Huu a3799576a2 add sentinelPassword option (#119) 2023-10-04 19:41:21 -04:00
Antony Jukesandajukes 63e72fbfce feat/MS Teams IP check improvement (#117)
* added ip in cidr utilities

* middleware add ms teams cidr check

* use cidr-matcher lib

* removed redundant require

* moved cidr-matcher require to top of file

* moved express require to top of file

---------

Co-authored-by: ajukes <ajukes@callable.io>
2023-09-18 10:35:04 -04:00
Dave Horton e5dce35bab more efficient checking of device calls (#118)
* more efficient checking of device calls

* when searching for matching gateways choose largest enclosing netmask
2023-09-15 10:53:22 -04:00
two56andMatt Preskett b6675de2bd Fix: Feature server REFER (#114)
* Update _onFeatureServerTransfer to fix REFER case

* Add INFO listener

---------

Co-authored-by: Matt Preskett <matt.preskett@netcall.com>
2023-08-20 08:47:01 -04:00
Dave Horton 66e73d8353 logging 2023-08-14 14:36:07 -04:00
Hoan Luu Huu a25aa08e17 update stats colector version (#111) 2023-08-07 21:22:47 -04:00
Hoan Luu Huu b1296c8e89 correct pare callRecording headers from sip info (#110) 2023-07-20 09:02:04 -04:00
Hoan Luu Huu 17df3a4ca8 Feat/siprec custom headers (#109)
* siprec custom headers

* siprec custom headers

* update siprec client util
2023-07-20 08:21:09 -04:00
Hoan Luu Huu f2e820915d Multi srs (#106)
* multi srs

* multi srs

* multi srs

* fix review comment
2023-07-04 16:41:46 +01:00
8 changed files with 859 additions and 1124 deletions
+1 -26
View File
@@ -14,28 +14,6 @@ assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR 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
})
} : null;
const Srf = require('drachtio-srf');
const srf = new Srf('sbc-inbound');
const opts = Object.assign({
@@ -93,10 +71,7 @@ const {
addToSet,
removeFromSet,
incrKey,
decrKey} = require('@jambonz/realtimedb-helpers')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
decrKey} = require('@jambonz/realtimedb-helpers')({}, logger);
const ngProtocol = process.env.JAMBONES_NG_PROTOCOL || 'udp';
const ngPort = process.env.RTPENGINE_PORT || ('udp' === ngProtocol ? 22222 : 8080);
+95 -38
View File
@@ -6,7 +6,8 @@ const {
SdpWantsSDES,
nudgeCallCounts,
roundTripTime,
parseConnectionIp
parseConnectionIp,
isPrivateVoipNetwork
} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
@@ -14,6 +15,7 @@ const {parseUri, stringifyUri, SipError} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-inbound');
const MS_TEAMS_USER_AGENT = 'Microsoft.PSTNHub.SIPProxy';
const MS_TEAMS_SIP_ENDPOINT = 'sip.pstnhub.microsoft.com';
const IMMUTABLE_HEADERS = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length'];
/**
* this is to make sure the outgoing From has the number in the incoming From
@@ -64,6 +66,7 @@ class CallSession extends Emitter {
this.application_sid = req.locals.application_sid;
this.account_sid = req.locals.account_sid;
this.service_provider_sid = req.locals.service_provider_sid;
this.srsClients = [];
}
get isFromMSTeams() {
@@ -116,6 +119,7 @@ class CallSession extends Emitter {
offer,
answer,
del,
query,
blockMedia,
unblockMedia,
blockDTMF,
@@ -130,6 +134,7 @@ class CallSession extends Emitter {
this.offer = offer;
this.answer = answer;
this.del = del;
this.query = query;
this.blockMedia = blockMedia;
this.unblockMedia = unblockMedia;
this.blockDTMF = blockDTMF;
@@ -153,7 +158,10 @@ class CallSession extends Emitter {
const wantsSrtp = this.req.locals.possibleWebRtcClient = SdpWantsSrtp(sdp);
const wantsSDES = SdpWantsSDES(sdp);
this.rtpEngineOpts = makeRtpEngineOpts(this.req, wantsSrtp, false, this.isFromMSTeams || wantsSDES);
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
this.rtpEngineResource = {
destroy: this.del.bind(null, this.rtpEngineOpts.common),
query: this.query.bind(null, this.rtpEngineOpts.common),
};
const obj = parseUri(this.req.uri);
let proxy, host, uri;
@@ -176,7 +184,7 @@ class CallSession extends Emitter {
...this.rtpEngineOpts.common,
...this.rtpEngineOpts.uac.mediaOpts,
'from-tag': this.rtpEngineOpts.uas.tag,
direction: ['public', 'private'],
direction: [isPrivateVoipNetwork(this.req.source_address) ? 'private' : 'public', 'private'],
sdp
};
const startAt = process.hrtime();
@@ -247,7 +255,8 @@ class CallSession extends Emitter {
'-Max-Forwards',
'-Record-Route',
'-Session-Expires',
'-X-Subspace-Forwarded-For'
'-X-Application-Sid',
'-X-Authenticated-User'
],
proxyResponseHeaders: ['all', '-X-Trace-ID'],
localSdpB: spdOfferB,
@@ -317,10 +326,7 @@ class CallSession extends Emitter {
this.activeCallIds.delete(callId);
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
if (this.srsClient) {
this.srsClient.stop();
this.srsClient = null;
}
this._stopRecording();
this.srf.endSession(this.req);
});
@@ -329,6 +335,13 @@ class CallSession extends Emitter {
dlg.on('modify', this._onReinvite.bind(this, dlg));
}
_stopRecording() {
if (this.srsClients.length) {
this.srsClients.forEach((c) => c.stop());
this.srsClients = [];
}
}
_setHandlers({uas, uac}) {
this.emit('connected');
const callStart = Date.now();
@@ -350,12 +363,24 @@ class CallSession extends Emitter {
this.uas = uas;
this.uac = uac;
[uas, uac].forEach((dlg) => {
dlg.on('destroy', async() => {
dlg.on('destroy', async(bye) => {
const other = dlg.other;
this.rtpEngineResource.destroy().catch((err) => {});
/* DH: need a better understanding of why query before delete is a good idea
this.rtpEngineResource.query()
.then((results) => {
this.logger.info({results}, 'rtpengine query results');
return this.rtpEngineResource.destroy();
})
.catch((err) => {});
*/
this.activeCallIds.delete(this.req.get('Call-ID'));
try {
await other.destroy();
const headers = {};
Object.keys(bye.headers).forEach((h) => {
if (!IMMUTABLE_HEADERS.includes(h)) headers[h] = bye.headers[h];
});
await other.destroy({headers});
} catch (err) {}
this.unsubscribeForDTMF();
@@ -412,10 +437,7 @@ class CallSession extends Emitter {
dlg.other = null;
other.other = null;
if (this.srsClient) {
this.srsClient.stop();
this.srsClient = null;
}
this._stopRecording();
this.logger.info(`call ended with normal termination, there are ${this.activeCallIds.size} active`);
this.srf.endSession(this.req);
@@ -689,6 +711,7 @@ Duration=${payload.duration} `
const to = this.req.getParsedHeader('To');
const aorFrom = from.uri;
const aorTo = to.uri;
const headers = contentType === 'application/json' && req.body ? JSON.parse(req.body) : {};
this.logger.info({to, from}, 'startCallRecording request for a call');
const srsUrl = req.get('X-Srs-Url');
@@ -696,7 +719,7 @@ Duration=${payload.duration} `
const callSid = req.get('X-Call-Sid');
const accountSid = req.get('X-Account-Sid');
const applicationSid = req.get('X-Application-Sid');
if (this.srsClient) {
if (this.srsClients.length) {
res.send(400);
this.logger.info('discarding duplicate startCallRecording request for a call');
return;
@@ -706,13 +729,14 @@ Duration=${payload.duration} `
res.send(400);
return;
}
this.srsClient = new SrsClient(this.logger, {
const arr = srsUrl.split(',');
this.srsClients = arr.map((url) => new SrsClient(this.logger, {
srf: dlg.srf,
direction: 'inbound',
originalInvite: this.req,
callingNumber: this.req.callingNumber,
calledNumber: this.req.calledNumber,
srsUrl,
srsUrl: url,
srsRecordingId,
callSid,
accountSid,
@@ -727,44 +751,74 @@ Duration=${payload.duration} `
del: this.del,
blockMedia: this.blockMedia,
unblockMedia: this.unblockMedia,
unsubscribe: this.unsubscribe
});
unsubscribe: this.unsubscribe,
headers
}));
try {
succeeded = await this.srsClient.start();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.start())
)).every((r) => r);
} catch (err) {
this.logger.error({err}, 'Error starting SipRec call recording');
}
}
else if (reason === 'stopCallRecording') {
if (!this.srsClient) {
if (!this.srsClients.length) {
res.send(400);
this.logger.info('discarding stopCallRecording request because we are not recording');
return;
}
try {
succeeded = await this.srsClient.stop();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.stop())
)).every((r) => r);
} catch (err) {
this.logger.error({err}, 'Error stopping SipRec call recording');
}
this.srsClient = null;
this.srsClients = [];
}
else if (reason === 'pauseCallRecording') {
if (!this.srsClient || this.srsClient.paused) {
if (!this.srsClients.length || this.srsClients.every((c) => c.paused)) {
this.logger.info('discarding invalid pauseCallRecording request');
res.send(400);
return;
}
succeeded = await this.srsClient.pause();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.pause())
)).every((r) => r);
}
else if (reason === 'resumeCallRecording') {
if (!this.srsClient || !this.srsClient.paused) {
if (!this.srsClients.length || !this.srsClients.every((c) => c.paused)) {
res.send(400);
this.logger.info('discarding invalid resumeCallRecording request');
return;
}
succeeded = await this.srsClient.resume();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.resume())
)).every((r) => r);
}
res.send(succeeded ? 200 : 503);
} else if (reason.includes('Dtmf')) {
const arr = /Signal=\s*([0-9#*])/.exec(req.body);
if (!arr) {
this.logger.info({body: req.body}, '_onInfo: invalid INFO Dtmf');
throw new Error(`_onInfo: no dtmf in body for ${contentType}`);
}
const code = arr[1];
const arr2 = /Duration=\s*(\d+)/.exec(req.body);
const duration = arr2 ? arr2[1] : 250;
const dtmfOpts = {
...this.rtpEngineOpts.common,
'from-tag': this.rtpEngineOpts.uac.tag,
code,
duration
};
const response = await this.playDTMF(dtmfOpts);
if ('ok' !== response.result) {
this.logger.info({response}, `rtpengine play Dtmf failed with ${JSON.stringify(response)}`);
throw new Error('rtpengine failed: answer');
}
res.send(200);
}
}
else if (dlg.type === 'uas' && ['application/dtmf-relay', 'application/dtmf'].includes(contentType)) {
@@ -802,10 +856,9 @@ Duration=${payload.duration} `
}
}
else {
const immutableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length'];
const headers = {};
Object.keys(req.headers).forEach((h) => {
if (!immutableHdrs.includes(h)) headers[h] = req.headers[h];
if (!IMMUTABLE_HEADERS.includes(h)) headers[h] = req.headers[h];
});
const response = await dlg.other.request({method: 'INFO', headers, body: req.body});
const responseHeaders = {};
@@ -815,8 +868,8 @@ Duration=${payload.duration} `
res.send(response.status, {headers: responseHeaders, body: response.body});
}
} catch (err) {
if (this.srsClient) {
this.srsClient = null;
if (this.srsClients.length) {
this.srsClients = [];
}
res.send(500);
this.logger.info({err}, `Error handing INFO request on ${dlg.type} leg`);
@@ -891,6 +944,7 @@ Duration=${payload.duration} `
this.uac = uac;
uac.other = this.uas;
this.uas.other = uac;
uac.on('info', this._onInfo.bind(this, uac));
uac.on('modify', this._onReinvite.bind(this, uac));
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
uac.on('destroy', () => {
@@ -900,19 +954,22 @@ Duration=${payload.duration} `
uac.other.destroy();
this.srf.endSession(this.req);
});
// now we can destroy the old dialog
dlg.destroy().catch(() => {});
// modify rtpengine to stream to new feature server
const opts = Object.assign({sdp: uac.remote.sdp, 'to-tag': res.getParsedHeader('To').params.tag},
this.rtpEngineOpts.answer);
const opts = {
...this.rtpEngineOpts.common,
'from-tag': this.rtpEngineOpts.uas.tag,
'to-tag': this.rtpEngineOpts.uac.tag,
sdp: uac.remote.sdp,
flags: ['port latching']
};
const response = await this.answer(opts);
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onFeatureServerTransfer: rtpengine failed: ${JSON.stringify(response)}`);
throw new Error(`_onFeatureServerTransfer: rtpengine answer failed: ${JSON.stringify(response)}`);
}
dlg.destroy().catch(() => {});
this.logger.info('successfully moved call to new feature server');
} catch (err) {
res.send(488);
this.logger.error(err, 'Error handling refer from feature server');
}
}
+54 -21
View File
@@ -45,7 +45,7 @@ AND vc.is_active = 1
AND sg.inbound = 1
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ?';
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ? AND is_active = 1';
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
const sqlApplicationBySid = 'SELECT * from applications WHERE application_sid = ?';
@@ -142,7 +142,7 @@ module.exports = (srf, logger) => {
const failure = {fromCarrier: false};
const uri = parseUri(req.uri);
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
const did = normalizeDID(req.calledNumber) || 'anonymous';
if (!isDotDecimal) {
/**
* The host part of the SIP URI is not a dot-decimal IP address,
@@ -153,24 +153,48 @@ module.exports = (srf, logger) => {
* Let's look for case #1 first...
*/
/* get all the carriers and gateways for the account owning this sip realm */
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, [uri.host]);
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
const gw = gwAcc.concat(gwSP);
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
if (selected) {
const [a] = await pp.query(sqlAccountByRealm, [uri.host]);
if (0 === a.length) return failure;
return {
fromCarrier: true,
gateway: selected,
service_provider_sid: a[0].service_provider_sid,
account_sid: a[0].account_sid,
application_sid: selected.application_sid,
account: a[0]
};
/* does anyone own this sip realm? */
const [a] = await pp.query(sqlAccountByRealm, [uri.host]);
if (a.length) {
assert(a.length === 1);
/* yes they do */
logger.debug(`sip realm is associated with account_sid: ${a[0].account_sid}`);
/**
* We have one of two cases:
* (1a). The user configured his or her carrier to send to their sip realm, or
* (1b). The user is making a call from a sip device.
*/
/* get all the carriers and gateways for the account owning this sip realm */
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, [uri.host]);
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
const gw = gwAcc
.concat(gwSP)
.sort((a, b) => b.netmask - a.netmask);
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
if (selected) {
const sql =
`SELECT application_sid FROM phone_numbers WHERE number = '${did}'
AND voip_carrier_sid = '${selected.voip_carrier_sid}'
AND account_sid = '${a[0].account_sid}'`;
logger.debug({selected, sql, did}, 'looking up DID');
const [r] = await pp.query(sql);
return {
fromCarrier: true,
gateway: selected,
service_provider_sid: a[0].service_provider_sid,
account_sid: a[0].account_sid,
application_sid: r[0]?.application_sid || selected.application_sid,
account: a[0]
};
}
return failure;
}
/* no match, so let's look for case #2 */
try {
logger.info({
@@ -178,7 +202,9 @@ module.exports = (srf, logger) => {
user: uri.user
}, 'sip realm is not associated with an account, checking carriers');
const [gw] = await pp.query(sqlSelectCarrierRequiringRegistration, [uri.host, uri.user]);
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
const matches = gw
.sort((a, b) => b.netmask - a.netmask)
.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
if (1 === matches.length) {
// bingo
//TODO: this assumes the carrier is associate to an account, not an SP
@@ -188,12 +214,19 @@ module.exports = (srf, logger) => {
const [a] = await pp.query(sqlAccountBySid, [[matches[0].account_sid]]);
if (0 === a.length) return failure;
logger.debug({matches}, `found registration carrier using ${uri.host} and ${uri.user}`);
const sql =
`SELECT application_sid FROM phone_numbers WHERE number = '${did}'
AND voip_carrier_sid = '${matches[0].voip_carrier_sid}'
AND account_sid = '${matches[0].account_sid}'`;
logger.debug({matches: matches[0], sql, did}, 'looking up DID');
const [r] = await pp.query(sql);
return {
fromCarrier: true,
gateway: matches[0],
service_provider_sid: a[0].service_provider_sid,
account_sid: a[0].account_sid,
application_sid: matches[0].application_sid,
application_sid: r[0]?.application_sid || matches[0].application_sid,
account: a[0]
};
}
@@ -229,6 +262,7 @@ module.exports = (srf, logger) => {
/* find all carrier entries that have an inbound gateway matching the source IP */
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
let matches = gw
.sort((a, b) => b.netmask - a.netmask)
.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address))
.map((gw) => {
return {
@@ -244,7 +278,6 @@ module.exports = (srf, logger) => {
if (matches.length) {
/* we have one or more matches. Now check for one with a provisioned phone number matching the DID */
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
const did = normalizeDID(req.calledNumber) || 'anonymous';
const sql = `SELECT * FROM phone_numbers WHERE number = '${did}' AND voip_carrier_sid IN (${vc_sids})`;
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
+5 -5
View File
@@ -1,7 +1,7 @@
const debug = require('debug')('jambonz:sbc-inbound');
const assert = require('assert');
const parseUri = require('drachtio-srf').parseUri;
const {nudgeCallCounts, roundTripTime} = require('./utils');
const {nudgeCallCounts, roundTripTime, isMSTeamsCIDR} = require('./utils');
const digestChallenge = require('@jambonz/digest-utils');
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
@@ -129,7 +129,7 @@ module.exports = function(srf, logger) {
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
return res.send(404, 'Number Not Provisioned');
}
logger.debug({gateway}, 'identifyAccount: incoming call from gateway');
logger.info({gateway}, 'identifyAccount: incoming call from gateway');
let sid;
if (siprec) {
@@ -155,8 +155,8 @@ module.exports = function(srf, logger) {
...req.locals
};
}
else if (msProxyIps.includes(req.source_address)) {
logger.debug({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
else if (msProxyIps.includes(req.source_address) || isMSTeamsCIDR(req.source_address)) {
logger.info({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
const uri = parseUri(req.uri);
const app = await lookupAppByTeamsTenant(uri.host);
@@ -178,7 +178,7 @@ module.exports = function(srf, logger) {
else {
req.locals.originator = 'user';
const uri = parseUri(req.uri);
logger.debug({source_address: req.source_address, realm: uri.host},
logger.info({source_address: req.source_address, realm: uri.host},
'identifyAccount: incoming user call');
const account = await lookupAccountBySipRealm(uri.host);
if (!account) {
+30 -4
View File
@@ -1,5 +1,8 @@
const CIDRMatcher = require('cidr-matcher');
const express = require('express');
const rtpCharacteristics = require('../data/rtp-transcoding');
const srtpCharacteristics = require('../data/srtp-transcoding');
let idx = 0;
const isWSS = (req) => {
@@ -19,8 +22,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
/* webrtc clients (e.g. sipjs) send DMTF via SIP INFO */
if ((srcIsUsingSrtp || dstIsUsingSrtp) && !teams) {
/* Allow Feature server to inject DTMF to both leg except call from Teams*/
if (!teams) {
dstOpts.flags.push('inject DTMF');
srcOpts.flags.push('inject DTMF');
}
@@ -96,7 +99,6 @@ const handleErrors = (logger, app, resolve, reject, e) => {
const createHealthCheckApp = (port, logger) => {
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
@@ -178,6 +180,28 @@ const parseConnectionIp = (sdp) => {
return arr ? arr[1] : null;
};
/**
* Checks if ip is one of MS Teams sip signalling ips
* https://learn.microsoft.com/en-us/azure/communication-services/concepts
* /telephony/direct-routing-infrastructure#sip-signaling-fqdns
* @param ip IP address, example 172.31.0.1
* */
const isMSTeamsCIDR = (ip) => {
const cidrs = [
'52.112.0.0/14',
'52.120.0.0/14'
];
const matcher = new CIDRMatcher(cidrs);
return matcher.contains(ip);
};
const isPrivateVoipNetwork = (ip) => {
if (process.env.PRIVATE_VOIP_NETWORK_CIDR) {
const matcher = new CIDRMatcher(process.env.PRIVATE_VOIP_NETWORK_CIDR.split(','));
return matcher.contains(ip);
}
return false;
};
module.exports = {
isWSS,
@@ -194,5 +218,7 @@ module.exports = {
createHealthCheckApp,
nudgeCallCounts,
roundTripTime,
parseConnectionIp
parseConnectionIp,
isMSTeamsCIDR,
isPrivateVoipNetwork
};
+665 -1023
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-inbound",
"version": "0.8.4",
"version": "0.8.5",
"main": "app.js",
"engines": {
"node": ">= 12.0.0"
@@ -25,12 +25,12 @@
"jslint": "eslint app.js lib"
},
"dependencies": {
"@jambonz/db-helpers": "^0.9.1",
"@jambonz/db-helpers": "^0.9.3",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.5",
"@jambonz/stats-collector": "^0.1.8",
"@jambonz/realtimedb-helpers": "^0.8.7",
"@jambonz/rtpengine-utils": "^0.4.4",
"@jambonz/siprec-client-utils": "^0.2.6",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.5",
"@jambonz/digest-utils": "^0.0.3",
"@aws-sdk/client-sns": "^3.360.0",
@@ -39,7 +39,7 @@
"cidr-matcher": "^2.1.1",
"debug": "^4.3.4",
"drachtio-fn-b2b-sugar": "0.0.12",
"drachtio-srf": "^4.5.21",
"drachtio-srf": "^4.5.31",
"express": "^4.18.1",
"pino": "^7.11.0",
"verify-aws-sns-signature": "^0.1.0",
+2
View File
@@ -36,6 +36,8 @@
Subject: uac-pcap-carrier-success
Content-Type: application/sdp
Content-Length: [len]
X-Authenticated-User: xhoaluu@jambonz.org
X-Application-Sid: APP_ID_1
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]