From fa7de71650248732872d05dd2f9a0224227f2f54 Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Sat, 8 Jan 2022 13:01:02 -0500 Subject: [PATCH] support for routing based on X-Jambonz-Routing header --- app.js | 12 ++- lib/call-session.js | 20 ++++- lib/middleware.js | 79 +++++++++++-------- test/docker-compose-testbed.yaml | 2 +- test/scenarios/uac-cancel.xml | 2 +- .../scenarios/uac-device-invalid-password.xml | 1 + test/scenarios/uac-device-unknown-realm.xml | 1 + test/scenarios/uac-device-unknown-user.xml | 1 + .../uac-pcap-carrier-fail-limits.xml | 1 + .../uac-pcap-carrier-success-reinvite.xml | 1 + test/scenarios/uac-pcap-carrier-success.xml | 1 + test/scenarios/uac-pcap-device-404.xml | 1 + ...-pcap-device-success-in-dialog-request.xml | 1 + test/scenarios/uac-pcap-device-success.xml | 1 + test/scenarios/uac-sip-uri-auth-success.xml | 4 +- 15 files changed, 87 insertions(+), 41 deletions(-) diff --git a/app.js b/app.js index fe38f99..c5bdd7b 100644 --- a/app.js +++ b/app.js @@ -45,7 +45,14 @@ const { database: process.env.JAMBONES_MYSQL_DATABASE, connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10 }, logger); -const {createHash, retrieveHash, incrKey, decrKey, retrieveSet} = require('@jambonz/realtimedb-helpers')({ +const { + createHash, + retrieveHash, + incrKey, + decrKey, + retrieveSet, + isMemberOfSet +} = require('@jambonz/realtimedb-helpers')({ host: process.env.JAMBONES_REDIS_HOST || 'localhost', port: process.env.JAMBONES_REDIS_PORT || 6379 }, logger); @@ -78,7 +85,8 @@ srf.locals = {...srf.locals, createHash, retrieveHash, incrKey, - decrKey + decrKey, + isMemberOfSet } }; const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, { diff --git a/lib/call-session.js b/lib/call-session.js index 286949d..d533a39 100644 --- a/lib/call-session.js +++ b/lib/call-session.js @@ -277,9 +277,23 @@ class CallSession extends Emitter { const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, { proxy, passFailure, - proxyRequestHeaders: ['all', '-X-MS-Teams-FQDN', '-X-MS-Teams-Tenant-FQDN', 'X-CID', '-Allow', - '-Session-Expires', '-X-Requested-Carrier-Sid', 'Min-SE'], - proxyResponseHeaders: ['all', '-Allow', '-Session-Expires'], + proxyRequestHeaders: [ + 'all', + '-X-MS-Teams-FQDN', + '-X-MS-Teams-Tenant-FQDN', + 'X-CID', + '-Allow', + '-Session-Expires', + '-X-Requested-Carrier-Sid', + '-X-Jambonz-Routing', + '-X-Jambonz-FS-UUID', + 'Min-SE' + ], + proxyResponseHeaders: [ + 'all', + '-Allow', + '-Session-Expires' + ], headers: hdrs, responseHeaders, auth: gw ? gw.auth : undefined, diff --git a/lib/middleware.js b/lib/middleware.js index bb02c9c..a553926 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -2,15 +2,10 @@ const debug = require('debug')('jambonz:sbc-outbound'); const parseUri = require('drachtio-srf').parseUri; const Registrar = require('@jambonz/mw-registrar'); const {selectHostPort, makeCallCountKey} = require('./utils'); - -const isLocalUri = (host, req) => { - debug({hostport: req.server.hostport}, `is ${host} local?`); - return req.server.hostport.includes(host); -}; - +const FS_UUID_SET_NAME = 'fsUUIDs'; module.exports = (srf, logger, opts) => { - const {incrKey, decrKey} = srf.locals.realtimeDbHelpers; + const {incrKey, decrKey, isMemberOfSet} = srf.locals.realtimeDbHelpers; const {stats} = srf.locals; const registrar = new Registrar(opts); const { @@ -34,6 +29,36 @@ module.exports = (srf, logger, opts) => { return req.srf.endSession(req); } + /* must come from a valid FS */ + if (!req.has('X-Jambonz-Routing')) { + logger.info({msg: req.msg}, 'missing X-Jambonz-Routing header'); + res.send(403, { + headers: { + 'X-Reason': 'missing required jambonz headers' + } + }); + return req.srf.endSession(req); + } + if (process.env.K8S) { + /* for K8S we do not use JAMBONES_CIDR so we must validate the sender by uuid FS creates */ + const fsUUID = req.get('X-Jambonz-FS-UUID'); + try { + const exists = await isMemberOfSet(FS_UUID_SET_NAME, fsUUID); + if (!exists || !fsUUID) { + res.send(403, { + headers: { + 'X-Reason': `missing or invalid FS-UUID ${fsUUID}` + } + }); + return req.srf.endSession(req); + } + } catch (err) { + res.send(500); + return req.srf.endSession(req); + } + } + + stats.increment('sbc.invites', ['direction:outbound']); req.on('cancel', () => { @@ -122,6 +147,7 @@ module.exports = (srf, logger, opts) => { const {lookupAccountBySipRealm} = req.srf.locals.dbHelpers; logger.info(`received outbound INVITE to ${req.uri} from server at ${req.server.hostport}`); const uri = parseUri(req.uri); + const desiredRouting = req.get('X-Jambonz-Routing'); if (!uri || !uri.user || !uri.host) { logger.info({uri: req.uri}, 'invalid request-uri on outbound call, rejecting'); @@ -132,21 +158,15 @@ module.exports = (srf, logger, opts) => { }); return req.srf.endSession(req); } - const aor = `${uri.user}@${uri.host}`; - let reg; - const dotDecimalHost = /^[0-9\.]+$/.test(uri.host); - debug(`received outbound INVITE to ${req.calledNumber} from server at ${req.server.hostport}`); - if (req.has('X-MS-Teams-FQDN') && req.has('X-MS-Teams-Tenant-FQDN')) { + if ('teams' === desiredRouting) { logger.debug('This is a call to ms teams'); req.locals.target = 'teams'; - return next(); } - else if (!dotDecimalHost) { - // uri host is not a dot-decimal address, so try to look up user - logger.debug(`searching for registered user ${aor}`); - reg = await registrar.query(aor); + else if ('user' === desiredRouting) { + const aor = `${uri.user}@${uri.host}`; + const reg = await registrar.query(aor); if (reg) { // user is registered..find out which sbc is handling it // us => we can put the call through @@ -161,35 +181,28 @@ module.exports = (srf, logger, opts) => { } req.locals.registration = reg; req.locals.target = 'user'; - return next(); } else { - // if the sip domain is one of ours return 404 const account = await lookupAccountBySipRealm(uri.host); if (account) { logger.info({host: uri.host, account}, `returning 404 to unregistered user in valid domain: ${req.uri}`); - res.send(404); - return req.srf.endSession(req); } + else { + logger.info({host: uri.host, account}, `returning 404 to user in invalid domain: ${req.uri}`); + } + res.send(404); + return req.srf.endSession(req); } } - if (!dotDecimalHost || !isLocalUri(uri.host, req)) { + else if ('sip' === desiredRouting) { // call that needs to be forwarded to a sip endpoint logger.info(`forwarding call to sip endpoint ${req.uri}`); req.locals.target = 'forward'; - return next(); } - - // if the called number is digits only (after possible leading plus sign), do lcr - if (!/^\d+$/.test(req.calledNumber.slice(1))) { - debug(`unable to route call to ${aor}; no registered user found`); - logger.info(`unable to route call to ${aor}; no registered user found`); - res.send(404); - return req.srf.endSession(req); + else if ('phone' === desiredRouting) { + debug('sending call to LCR'); + req.locals.target = 'lcr'; } - - debug('sending call to LCR'); - req.locals.target = 'lcr'; next(); }; diff --git a/test/docker-compose-testbed.yaml b/test/docker-compose-testbed.yaml index 949a01f..c178427 100644 --- a/test/docker-compose-testbed.yaml +++ b/test/docker-compose-testbed.yaml @@ -118,7 +118,7 @@ services: ipv4_address: 172.39.0.24 influxdb: - image: influxdb:1.8-alpine + image: influxdb:1.8 ports: - "8086:8086" networks: diff --git a/test/scenarios/uac-cancel.xml b/test/scenarios/uac-cancel.xml index 40defa3..1386013 100644 --- a/test/scenarios/uac-cancel.xml +++ b/test/scenarios/uac-cancel.xml @@ -36,6 +36,7 @@ Subject: uac-pcap-carrier-success X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: phone Content-Type: application/sdp Content-Length: [len] @@ -103,4 +104,3 @@ - diff --git a/test/scenarios/uac-device-invalid-password.xml b/test/scenarios/uac-device-invalid-password.xml index 747897c..797724d 100644 --- a/test/scenarios/uac-device-invalid-password.xml +++ b/test/scenarios/uac-device-invalid-password.xml @@ -34,6 +34,7 @@ Contact: sip:sipp@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-device-unknown-user + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-device-unknown-realm.xml b/test/scenarios/uac-device-unknown-realm.xml index 567ceec..d5cf774 100644 --- a/test/scenarios/uac-device-unknown-realm.xml +++ b/test/scenarios/uac-device-unknown-realm.xml @@ -34,6 +34,7 @@ Contact: sip:sipp@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-device-unknown-user + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-device-unknown-user.xml b/test/scenarios/uac-device-unknown-user.xml index 2ab433f..f0bbb9e 100644 --- a/test/scenarios/uac-device-unknown-user.xml +++ b/test/scenarios/uac-device-unknown-user.xml @@ -34,6 +34,7 @@ Contact: sip:sipp@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-device-unknown-user + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-carrier-fail-limits.xml b/test/scenarios/uac-pcap-carrier-fail-limits.xml index 51bb8cf..fb0d32f 100644 --- a/test/scenarios/uac-pcap-carrier-fail-limits.xml +++ b/test/scenarios/uac-pcap-carrier-fail-limits.xml @@ -16,6 +16,7 @@ Max-Forwards: 70 X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: ff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: phone Subject: uac-pcap-carrier-fail-limits Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-carrier-success-reinvite.xml b/test/scenarios/uac-pcap-carrier-success-reinvite.xml index dd79bd7..97e6565 100644 --- a/test/scenarios/uac-pcap-carrier-success-reinvite.xml +++ b/test/scenarios/uac-pcap-carrier-success-reinvite.xml @@ -17,6 +17,7 @@ Subject: uac-pcap-carrier-success X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: phone Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-carrier-success.xml b/test/scenarios/uac-pcap-carrier-success.xml index be95bfd..b5ff400 100644 --- a/test/scenarios/uac-pcap-carrier-success.xml +++ b/test/scenarios/uac-pcap-carrier-success.xml @@ -16,6 +16,7 @@ Max-Forwards: 70 X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: phone Subject: uac-pcap-carrier-success Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-device-404.xml b/test/scenarios/uac-pcap-device-404.xml index 40c761d..b8df157 100644 --- a/test/scenarios/uac-pcap-device-404.xml +++ b/test/scenarios/uac-pcap-device-404.xml @@ -37,6 +37,7 @@ Subject: uac-pcap-device-success X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: ff649e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-device-success-in-dialog-request.xml b/test/scenarios/uac-pcap-device-success-in-dialog-request.xml index 59f9fa2..78a4216 100644 --- a/test/scenarios/uac-pcap-device-success-in-dialog-request.xml +++ b/test/scenarios/uac-pcap-device-success-in-dialog-request.xml @@ -34,6 +34,7 @@ Contact: sip:sipp@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-pcap-device-success + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-pcap-device-success.xml b/test/scenarios/uac-pcap-device-success.xml index 1d5516a..85d7785 100644 --- a/test/scenarios/uac-pcap-device-success.xml +++ b/test/scenarios/uac-pcap-device-success.xml @@ -35,6 +35,7 @@ Contact: sip:sipp@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-pcap-device-success + X-Jambonz-Routing: user Content-Type: application/sdp Content-Length: [len] diff --git a/test/scenarios/uac-sip-uri-auth-success.xml b/test/scenarios/uac-sip-uri-auth-success.xml index acc220f..504e13f 100644 --- a/test/scenarios/uac-sip-uri-auth-success.xml +++ b/test/scenarios/uac-sip-uri-auth-success.xml @@ -37,6 +37,7 @@ Subject: uac-sip-uri-auth-success X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: sip Content-Type: application/sdp Content-Length: [len] @@ -90,6 +91,7 @@ Subject: uac-sip-uri-auth-success X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803 X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803 + X-Jambonz-Routing: sip Content-Type: application/sdp Content-Length: [len] @@ -126,7 +128,7 @@ To: [peer_tag_param] [last_Call-ID:] CSeq: 2 ACK - Subject: uac-sip-uri-auth-success + Subject: Content-Length: 0 ]]>