Compare commits

...
7 Commits
Author SHA1 Message Date
Dave Horton 67055ad8aa bump version 2023-02-24 10:06:37 -05:00
Dave Horton 6b348aaf3a pass on displayName of From header if we get it 2023-02-23 08:48:08 -05:00
Dave Horton 0c9df878b8 update dockerfile 2023-02-23 08:23:17 -05:00
Dave Horton 93930fff18 fix for #79 (#80) 2023-02-22 08:51:35 -05:00
Dave Horton 2ce24659ca initial attempt (#77)
* initial attempt

* fix: special handling on uas dialog
2023-02-21 14:00:36 -05:00
EgleH 7c10e98a66 Upgrade node to node:18.14.0-alpine3.16 (#78) 2023-02-21 07:54:55 -05:00
Dave Horton f3fe46319d update siprec client with fix for stop recording 2023-02-17 12:10:14 -05:00
5 changed files with 98 additions and 31 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM --platform=linux/amd64 node:18-alpine3.16 as base
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
+65 -11
View File
@@ -22,8 +22,10 @@ const MS_TEAMS_SIP_ENDPOINT = 'sip.pstnhub.microsoft.com';
const createBLegFromHeader = (req) => {
const from = req.getParsedHeader('From');
const uri = parseUri(from.uri);
if (uri && uri.user) return `<sip:${uri.user}@localhost>`;
return '<sip:anonymous@localhost>';
const name = from.name;
const displayName = name ? `${name} ` : '';
if (uri && uri.user) return `${displayName}<sip:${uri.user}@localhost>`;
else return `${displayName}<sip:anonymous@localhost>`;
};
const createSiprecBody = (headers, sdp, type, content) => {
@@ -426,6 +428,12 @@ class CallSession extends Emitter {
// default forwarding of other request types
forwardInDialogRequests(uas, ['notify', 'options', 'message']);
// we need special handling for invite with null sdp followed by 3pcc re-invite
if (uas.local.sdp.includes('a=recvonly') || uas.local.sdp.includes('a=inactive')) {
this.logger.info('incoming call is recvonly or inactive, waiting for re-invite');
this._recvonly = true;
}
}
async _onDTMF(dlg, payload) {
@@ -528,24 +536,70 @@ Duration=${payload.duration} `
}
async _onReinvite(dlg, req, res) {
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
const reason = req.get('X-Reason');
const isReleasingMedia = reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason);
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
const direction = dlg.type === 'uas' ? ['public', 'private'] : ['private', 'public'];
try {
/* check for re-invite with no SDP -- seen that from BT when they provide UUI info */
if (!req.body) {
this.logger.info('got a reINVITE with no SDP; just respond with our current offer');
res.send(200, {body: dlg.local.sdp});
if (dlg.type === 'uas' && this._recvonly) {
/* seen this from Broadworks - initial INVITE has no SDP, then reINVITE with SDP */
this._recvonly = false; //one-time only
const myMungedSdp = dlg.local.sdp.replace('a=recvonly', 'a=sendrecv').replace('a=inactive', 'a=sendrecv');
this.logger.info({myMungedSdp}, '_onReinvite (3gpp): got a reINVITE with no SDP while in recvonly mode');
res.send(200,
{
body: myMungedSdp
},
(err, req) => {},
async(ack) => {
const remoteOffer = ack.body;
this.logger.info({remoteOffer}, '_onReinvite (3gpp): got ACK for reINVITE with SDP');
let opts = {
...this.rtpEngineOpts.common,
...offerMedia,
'from-tag': fromTag,
'to-tag': toTag,
direction,
sdp: remoteOffer,
};
let response = await this.offer(opts);
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite (3gpp): rtpengine failed: offer: ${JSON.stringify(response)}`);
}
this.logger.info({response}, '_onReinvite (3gpp): response from rtpengine for offer');
const fsSdp = await dlg.other.modify(response.sdp);
opts = {
...this.rtpEngineOpts.common,
...answerMedia,
'from-tag': fromTag,
'to-tag': toTag,
sdp: fsSdp
};
response = await this.answer(opts);
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite(3gpp): rtpengine failed answer: ${JSON.stringify(response)}`);
}
}
);
}
else {
this.logger.info('got a reINVITE with no SDP; just respond with our current offer');
res.send(200, {body: dlg.local.sdp});
}
return;
}
const offeredSdp = Array.isArray(req.payload) && req.payload.length > 1 ?
req.payload.find((p) => p.type === 'application/sdp').content :
req.body;
const reason = req.get('X-Reason');
const isReleasingMedia = reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason);
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
const direction = dlg.type === 'uas' ? ['public', 'private'] : ['private', 'public'];
if (isReleasingMedia) {
if (!offerMedia.flags.includes('asymmetric')) offerMedia.flags.push('asymmetric');
offerMedia.flags = offerMedia.flags.filter((f) => f !== 'media handover');
+15 -2
View File
@@ -28,7 +28,8 @@ vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ip
FROM sip_gateways sg, voip_carriers vc
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
AND vc.service_provider_sid IS NOT NULL
AND vc.is_active = 1`;
AND vc.is_active = 1
AND sg.inbound = 1`;
const sqlCarriersForAccountBySid =
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
@@ -210,7 +211,19 @@ module.exports = (srf, logger) => {
else {
/* find all carrier entries that have an inbound gateway matching the source IP */
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
let matches = gw
.filter(gatewayMatchesSourceAddress.bind(null, req.source_address))
.map((gw) => {
return {
voip_carrier_sid: gw.voip_carrier_sid,
name: gw.name,
service_provider_sid: gw.service_provider_sid,
account_sid: gw.account_sid,
application_sid: gw.application_sid
};
});
/* remove duplicates, winnow down to voip_carriers, not gateways */
matches = [...new Set(matches.map(JSON.stringify))].map(JSON.parse);
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(',');
+15 -15
View File
@@ -1,12 +1,12 @@
{
"name": "sbc-inbound",
"version": "v0.8.0",
"version": "v0.8.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "sbc-inbound",
"version": "v0.8.0",
"version": "v0.8.1",
"license": "MIT",
"dependencies": {
"@jambonz/db-helpers": "^0.7.4",
@@ -14,7 +14,7 @@
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.6.3",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.3",
"@jambonz/siprec-client-utils": "^0.2.4",
"@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.2.5",
"aws-sdk": "^2.1261.0",
@@ -700,9 +700,9 @@
}
},
"node_modules/@jambonz/siprec-client-utils": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.3.tgz",
"integrity": "sha512-WVFrlFvxHWMd40DyVGUcrU0CVdS6mFje0n284I1Xn/z+iuaPklQ5p8KXSMAjqPz38/BaFPwtibLSvpp6CSuwwg==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.4.tgz",
"integrity": "sha512-ptwY1crXmYYT0WtkMzxtVyEuH8WD0F4Za3K7YQei3bVMdHS+w2CwndfRWGlectkFTuG/eYhTCPrNzD7oIcRyjg==",
"dependencies": {
"sdp-transform": "^2.14.1",
"uuid": "^8.3.2"
@@ -6868,9 +6868,9 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/undici": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz",
"integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==",
"version": "5.19.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.19.1.tgz",
"integrity": "sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==",
"dependencies": {
"busboy": "^1.6.0"
},
@@ -7823,9 +7823,9 @@
}
},
"@jambonz/siprec-client-utils": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.3.tgz",
"integrity": "sha512-WVFrlFvxHWMd40DyVGUcrU0CVdS6mFje0n284I1Xn/z+iuaPklQ5p8KXSMAjqPz38/BaFPwtibLSvpp6CSuwwg==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.4.tgz",
"integrity": "sha512-ptwY1crXmYYT0WtkMzxtVyEuH8WD0F4Za3K7YQei3bVMdHS+w2CwndfRWGlectkFTuG/eYhTCPrNzD7oIcRyjg==",
"requires": {
"sdp-transform": "^2.14.1",
"uuid": "^8.3.2"
@@ -12456,9 +12456,9 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"undici": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz",
"integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==",
"version": "5.19.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.19.1.tgz",
"integrity": "sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==",
"requires": {
"busboy": "^1.6.0"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-inbound",
"version": "v0.8.0",
"version": "v0.8.1",
"main": "app.js",
"engines": {
"node": ">= 12.0.0"
@@ -30,7 +30,7 @@
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.6.3",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.3",
"@jambonz/siprec-client-utils": "^0.2.4",
"@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.2.5",
"aws-sdk": "^2.1261.0",