mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 18:44:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2747bc15d6 | ||
|
|
cdfbf384fe | ||
|
|
96a83619c7 | ||
|
|
b8a67019a1 | ||
|
|
0e1627ba82 |
+43
-5
@@ -15,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
|
||||
@@ -118,6 +119,7 @@ class CallSession extends Emitter {
|
||||
offer,
|
||||
answer,
|
||||
del,
|
||||
query,
|
||||
blockMedia,
|
||||
unblockMedia,
|
||||
blockDTMF,
|
||||
@@ -132,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;
|
||||
@@ -155,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;
|
||||
|
||||
@@ -357,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();
|
||||
|
||||
@@ -780,6 +798,27 @@ Duration=${payload.duration} `
|
||||
)).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)) {
|
||||
@@ -817,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 = {};
|
||||
|
||||
+17
-4
@@ -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,
|
||||
@@ -175,12 +175,19 @@ module.exports = (srf, logger) => {
|
||||
.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: selected.application_sid,
|
||||
application_sid: r[0]?.application_sid || selected.application_sid,
|
||||
account: a[0]
|
||||
};
|
||||
}
|
||||
@@ -207,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]
|
||||
};
|
||||
}
|
||||
@@ -264,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');
|
||||
|
||||
|
||||
+2
-2
@@ -22,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');
|
||||
}
|
||||
|
||||
Generated
+22
-22
@@ -11,11 +11,11 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-auto-scaling": "^3.360.0",
|
||||
"@aws-sdk/client-sns": "^3.360.0",
|
||||
"@jambonz/db-helpers": "^0.9.1",
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/digest-utils": "^0.0.3",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@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",
|
||||
@@ -1797,9 +1797,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/db-helpers": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.1.tgz",
|
||||
"integrity": "sha512-asQQdeQEl1jCyQAxp2kMljZzExQbcG/mBxVYA2Jf0E1ReZctC206LWMWmY/rvbSHHXNZBzJlNxhD0dQB+FtJYA==",
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.3.tgz",
|
||||
"integrity": "sha512-3XFs7NC7J7Q/eb1CwG1YJHa6N4elh8IP/4hMMDgoM9U5Loplx61XI4nZ58FIrY3C/F6gEF4UdjqKvbusEVw7cQ==",
|
||||
"dependencies": {
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1840,12 +1840,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/rtpengine-utils": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/rtpengine-utils/-/rtpengine-utils-0.4.3.tgz",
|
||||
"integrity": "sha512-Aca2QHINtYfFTORag7I7WCw0IqTzx7ZNnboj99XshVF+kcP+AN7qbzUCMYMVC36QXCkHNP7ExhVaBWB2e3Cahg==",
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/rtpengine-utils/-/rtpengine-utils-0.4.4.tgz",
|
||||
"integrity": "sha512-Kta5MlCPAnb4Z9yxzbPiRThI3yYbeBEslh00/g0rXSEK3voMNpQFPVMy9d83EAOoMEIMouas7/D2gVwV+2hfHA==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.1",
|
||||
"rtpengine-client": "^0.4.11",
|
||||
"rtpengine-client": "^0.4.12",
|
||||
"ws": "^8.5.0"
|
||||
}
|
||||
},
|
||||
@@ -5128,9 +5128,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rtpengine-client": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.4.11.tgz",
|
||||
"integrity": "sha512-upWU+aDQ9SWzyL8gSoCWelInnkELR8c2Qlym9iCupwAmJb6SztZDP/wwZDWVKhusHI4o0ihx077AZrgv3fWwAg==",
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.4.12.tgz",
|
||||
"integrity": "sha512-Ax7Xf0ct4ZsjX+BzW/t3l19Lux4Ia6q+9BwWWcnelqi8IF0vts9OGcC3EV1/oBlVUCKnbjSYyG6kmUU5xC65vw==",
|
||||
"dependencies": {
|
||||
"bencode": "^2.0.3",
|
||||
"uuid-random": "^1.3.2",
|
||||
@@ -7498,9 +7498,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@jambonz/db-helpers": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.1.tgz",
|
||||
"integrity": "sha512-asQQdeQEl1jCyQAxp2kMljZzExQbcG/mBxVYA2Jf0E1ReZctC206LWMWmY/rvbSHHXNZBzJlNxhD0dQB+FtJYA==",
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.3.tgz",
|
||||
"integrity": "sha512-3XFs7NC7J7Q/eb1CwG1YJHa6N4elh8IP/4hMMDgoM9U5Loplx61XI4nZ58FIrY3C/F6gEF4UdjqKvbusEVw7cQ==",
|
||||
"requires": {
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
@@ -7538,12 +7538,12 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/rtpengine-utils": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/rtpengine-utils/-/rtpengine-utils-0.4.3.tgz",
|
||||
"integrity": "sha512-Aca2QHINtYfFTORag7I7WCw0IqTzx7ZNnboj99XshVF+kcP+AN7qbzUCMYMVC36QXCkHNP7ExhVaBWB2e3Cahg==",
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/rtpengine-utils/-/rtpengine-utils-0.4.4.tgz",
|
||||
"integrity": "sha512-Kta5MlCPAnb4Z9yxzbPiRThI3yYbeBEslh00/g0rXSEK3voMNpQFPVMy9d83EAOoMEIMouas7/D2gVwV+2hfHA==",
|
||||
"requires": {
|
||||
"debug": "^4.3.1",
|
||||
"rtpengine-client": "^0.4.11",
|
||||
"rtpengine-client": "^0.4.12",
|
||||
"ws": "^8.5.0"
|
||||
}
|
||||
},
|
||||
@@ -10022,9 +10022,9 @@
|
||||
}
|
||||
},
|
||||
"rtpengine-client": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.4.11.tgz",
|
||||
"integrity": "sha512-upWU+aDQ9SWzyL8gSoCWelInnkELR8c2Qlym9iCupwAmJb6SztZDP/wwZDWVKhusHI4o0ihx077AZrgv3fWwAg==",
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/rtpengine-client/-/rtpengine-client-0.4.12.tgz",
|
||||
"integrity": "sha512-Ax7Xf0ct4ZsjX+BzW/t3l19Lux4Ia6q+9BwWWcnelqi8IF0vts9OGcC3EV1/oBlVUCKnbjSYyG6kmUU5xC65vw==",
|
||||
"requires": {
|
||||
"bencode": "^2.0.3",
|
||||
"uuid-random": "^1.3.2",
|
||||
|
||||
+2
-2
@@ -25,10 +25,10 @@
|
||||
"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.7",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@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",
|
||||
|
||||
Reference in New Issue
Block a user