diff --git a/app.js b/app.js index 968f440..110d583 100644 --- a/app.js +++ b/app.js @@ -83,7 +83,18 @@ srf.locals = {...srf.locals, retrieveSet } }; -srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger); +const { + wasOriginatedFromCarrier, + getApplicationForDidAndCarrier, + getOutboundGatewayForRefer +} = require('./lib/db-utils')(srf, logger); +srf.locals = { + ...srf.locals, + wasOriginatedFromCarrier, + getApplicationForDidAndCarrier, + getOutboundGatewayForRefer, + getFeatureServer: require('./lib/fs-tracking')(srf, logger) +}; const activeCallIds = srf.locals.activeCallIds; const { diff --git a/lib/call-session.js b/lib/call-session.js index b9470ff..ce1a41d 100644 --- a/lib/call-session.js +++ b/lib/call-session.js @@ -1,7 +1,7 @@ const Emitter = require('events'); const {makeRtpEngineOpts, SdpWantsSrtp, makeCallCountKey} = require('./utils'); const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar'); -const {parseUri, SipError} = require('drachtio-srf'); +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'; @@ -469,11 +469,42 @@ Duration=${payload.duration} ` try { const referTo = req.getParsedHeader('Refer-To'); const uri = parseUri(referTo.uri); - this.logger.info({uri, referTo}, 'received REFER from feature server'); + this.logger.info({uri, referTo, headers: req.headers}, 'received REFER from feature server'); const arr = /context-(.*)/.exec(uri.user); if (!arr) { - this.logger.info(`invalid Refer-To header: ${referTo.uri}`); - return res.send(501); + /* call transfer requested */ + const {gateway} = this.req.locals; + const referredBy = req.getParsedHeader('Referred-By'); + if (!referredBy) return res.send(400); + const u = parseUri(referredBy.uri); + + let selectedGateway = false; + let e164 = false; + if (gateway) { + /* host of Refer-to to an outbound gateway */ + const gw = await this.srf.locals.getOutboundGatewayForRefer(gateway.voip_carrier_sid); + if (gw) { + selectedGateway = true; + e164 = gw.e164_leading_plus; + uri.host = gw.ipv4; + uri.port = gw.port; + } + } + if (!selectedGateway) { + uri.host = this.req.source_address; + uri.port = this.req.source_port; + } + if (e164 && !uri.user.startsWith('+')) { + uri.user = `+${uri.user}`; + } + const response = await this.uas.request({ + method: 'REFER', + headers: { + 'Refer-To': stringifyUri(uri), + 'Referred-By': stringifyUri(u) + } + }); + return res.send(response.status); } res.send(202); diff --git a/lib/db-utils.js b/lib/db-utils.js index 03b3132..50cefab 100644 --- a/lib/db-utils.js +++ b/lib/db-utils.js @@ -35,6 +35,13 @@ SELECT * FROM phone_numbers WHERE number = ? AND voip_carrier_sid = ?`; +const sqlSelectOutboundGatewayForCarrier = ` +SELECT ipv4, port, e164_leading_plus +FROM sip_gateways sg, voip_carriers vc +WHERE sg.voip_carrier_sid = ? +AND sg.voip_carrier_sid = vc.voip_carrier_sid +AND outbound = 1`; + const gatewayMatchesSourceAddress = (source_address, gw) => { if (32 === gw.netmask && gw.ipv4 === source_address) return true; if (gw.netmask < 32) { @@ -48,6 +55,19 @@ module.exports = (srf, logger) => { const {pool} = srf.locals.dbHelpers; const pp = pool.promise(); + const getOutboundGatewayForRefer = async(voip_carrier_sid) => { + try { + const [r] = await pp.query(sqlSelectOutboundGatewayForCarrier, [voip_carrier_sid]); + if (0 === r.length) return null; + + /* if multiple, prefer a DNS name */ + const hasDns = r.find((row) => row.ipv4.match(/^[A-Za-z]/)); + return hasDns || r[0]; + } catch (err) { + logger.error({err}, 'getOutboundGatewayForRefer'); + } + }; + const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => { const did = normalizeDID(req.calledNumber); @@ -125,6 +145,7 @@ module.exports = (srf, logger) => { return { wasOriginatedFromCarrier, - getApplicationForDidAndCarrier + getApplicationForDidAndCarrier, + getOutboundGatewayForRefer }; }; diff --git a/lib/middleware.js b/lib/middleware.js index 1d40fcd..e4b6294 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -68,8 +68,6 @@ module.exports = function(srf, logger) { blacklistUnknownRealms: true, emitter: new AuthOutcomeReporter(stats) }); - const {wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = require('./db-utils')(srf, logger); - const initLocals = (req, res, next) => { req.locals = req.locals || {}; @@ -113,7 +111,7 @@ module.exports = function(srf, logger) { const identifyAccount = async(req, res, next) => { try { - + const {wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = req.srf.locals; const {fromCarrier, gateway, account_sid, application_sid, account} = await wasOriginatedFromCarrier(req); /** * calls come from 3 sources: @@ -133,6 +131,7 @@ module.exports = function(srf, logger) { req.locals = { originator: 'trunk', carrier: gateway.name, + gateway, application_sid: sid || gateway.application_sid, account_sid, account, diff --git a/package-lock.json b/package-lock.json index 0a67ad4..0d9f6a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "cidr-matcher": "^2.1.1", "debug": "^4.3.1", "drachtio-fn-b2b-sugar": "0.0.12", - "drachtio-srf": "^4.4.55", + "drachtio-srf": "^4.4.59", "express": "^4.17.1", "pino": "^6.8.0", "rtpengine-client": "^0.2.0", @@ -1409,9 +1409,9 @@ } }, "node_modules/drachtio-sip": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/drachtio-sip/-/drachtio-sip-0.6.0.tgz", - "integrity": "sha512-C8Y33rVpP0KwmZmBMxBjhbj58kktVFlzc+Od2g6TgOaqeEyF0JhwrHnech+iEtr2A2eKBlA85C9cCRh1+QpoRA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/drachtio-sip/-/drachtio-sip-0.6.2.tgz", + "integrity": "sha512-BkiRZq3Yq2WVSGY3M7Hv4yX4dIW/o0/4xNMcm26IxT71YIRy07UtbQUHaMI3P2HfPu5zK6RoQW2MHrxPXtz6ZQ==", "dependencies": { "debug": "^4.3.1", "eslint-plugin-promise": "^5.1.0", @@ -1419,9 +1419,9 @@ } }, "node_modules/drachtio-sip/node_modules/eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", + "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", "engines": { "node": "^10.12.0 || >=12.0.0" }, @@ -1430,15 +1430,15 @@ } }, "node_modules/drachtio-srf": { - "version": "4.4.55", - "resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.55.tgz", - "integrity": "sha512-wADXzcEdxD748iSK2KepD9LEiA+XW0nE2zNV89azKk0AafZGD0+DLMf1m8IOBnFE30H91pJI74Z8fO652+QB0A==", + "version": "4.4.59", + "resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.59.tgz", + "integrity": "sha512-hrW9bZ8TZR9JQ3pqI+nyrI1eAzEOwHuvm1lNL1fbmZmRddKJzYdylkgVoyURs/OlT/nANy/M43GrQjcGP4psPw==", "dependencies": { "async": "^1.4.2", "debug": "^3.2.7", "delegates": "^0.1.0", "deprecate": "^1.1.1", - "drachtio-sip": "^0.6.0", + "drachtio-sip": "^0.6.2", "node-noop": "0.0.1", "only": "0.0.2", "sdp-transform": "^2.14.1", @@ -5850,9 +5850,9 @@ "requires": {} }, "drachtio-sip": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/drachtio-sip/-/drachtio-sip-0.6.0.tgz", - "integrity": "sha512-C8Y33rVpP0KwmZmBMxBjhbj58kktVFlzc+Od2g6TgOaqeEyF0JhwrHnech+iEtr2A2eKBlA85C9cCRh1+QpoRA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/drachtio-sip/-/drachtio-sip-0.6.2.tgz", + "integrity": "sha512-BkiRZq3Yq2WVSGY3M7Hv4yX4dIW/o0/4xNMcm26IxT71YIRy07UtbQUHaMI3P2HfPu5zK6RoQW2MHrxPXtz6ZQ==", "requires": { "debug": "^4.3.1", "eslint-plugin-promise": "^5.1.0", @@ -5860,23 +5860,23 @@ }, "dependencies": { "eslint-plugin-promise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.0.tgz", - "integrity": "sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.1.1.tgz", + "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", "requires": {} } } }, "drachtio-srf": { - "version": "4.4.55", - "resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.55.tgz", - "integrity": "sha512-wADXzcEdxD748iSK2KepD9LEiA+XW0nE2zNV89azKk0AafZGD0+DLMf1m8IOBnFE30H91pJI74Z8fO652+QB0A==", + "version": "4.4.59", + "resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.4.59.tgz", + "integrity": "sha512-hrW9bZ8TZR9JQ3pqI+nyrI1eAzEOwHuvm1lNL1fbmZmRddKJzYdylkgVoyURs/OlT/nANy/M43GrQjcGP4psPw==", "requires": { "async": "^1.4.2", "debug": "^3.2.7", "delegates": "^0.1.0", "deprecate": "^1.1.1", - "drachtio-sip": "^0.6.0", + "drachtio-sip": "^0.6.2", "node-noop": "0.0.1", "only": "0.0.2", "sdp-transform": "^2.14.1", diff --git a/package.json b/package.json index 2ae20aa..6b3740a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "cidr-matcher": "^2.1.1", "debug": "^4.3.1", "drachtio-fn-b2b-sugar": "0.0.12", - "drachtio-srf": "^4.4.55", + "drachtio-srf": "^4.4.59", "pino": "^6.8.0", "rtpengine-client": "^0.2.0" },