From f40e21738e8095a716d06df227a43a89e551657f Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Wed, 9 Mar 2022 16:09:04 -0500 Subject: [PATCH] wip: if lookup by realm fails try also source IP --- lib/db-utils.js | 182 ++++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 122 insertions(+), 62 deletions(-) diff --git a/lib/db-utils.js b/lib/db-utils.js index f21069b..f0389cd 100644 --- a/lib/db-utils.js +++ b/lib/db-utils.js @@ -28,6 +28,16 @@ WHERE sg.voip_carrier_sid = vc.voip_carrier_sid AND vc.service_provider_sid IS NOT NULL AND vc.is_active = 1`; +const sqlSelectAllGatewaysForAccountBySourceAddress = +`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid, +vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask +FROM sip_gateways sg, voip_carriers vc +WHERE sg.voip_carrier_sid = vc.voip_carrier_sid +AND sg.ipv4 = ? +AND sg.inbound = 1 +AND vc.account_sid IS NOT NULL +AND vc.is_active = 1`; + const sqlCarriersForAccountBySid = `SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask @@ -94,82 +104,132 @@ module.exports = (srf, logger) => { const uri = parseUri(req.uri); const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host); - if (isDotDecimal) { - if (process.env.JAMBONES_HOSTING) { - if (!process.env.SBC_ACCOUNT_SID) return failure; + logger.debug(`wasOriginatedFromCarrier: realm ${uri.host}, source ${req.source_address}`); - /* look for carrier only within that account */ - const [r] = await pp.query(sqlCarriersForAccountBySid, - [process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]); - if (0 === r.length) return failure; - return { - fromCarrier: true, - gateway: r[0], - account_sid: process.env.SBC_ACCOUNT_SID - }; + /* hosted jambonz -- accounts must configure carriers to send to their sip realm */ + if (isDotDecimal && process.env.JAMBONES_HOSTING) { + if (!process.env.SBC_ACCOUNT_SID) { + logger.debug(`wasOriginatedFromCarrier: hosted jambonz ${process.env.SBC_ACCOUNT_SID}`); + return failure; } - else { - /* we may have a carrier at the service provider level */ - const [gw] = await pp.query(sqlSelectAllGatewaysForSP); - const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address)); - if (matches.length) { - /* we have one or more carriers that match. Now we need to find one with a provisioned phone number */ - const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(','); - const did = normalizeDID(req.calledNumber); - 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'); - const [r] = await pp.query(sql); - if (0 === r.length) { - /* came from a carrier, but number is not provisioned.. - check if we only have a single account, otherwise we have no - way of knowing which account this is for - */ - const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?', - matches[0].service_provider_sid); - if (r[0].count === 0) return {fromCarrier: true}; - else { - const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?', - matches[0].service_provider_sid); - return { - fromCarrier: true, - gateway: matches[0], - account_sid: accounts[0].account_sid, - account: accounts[0] - }; - } - } - const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid); - const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid); - assert(accounts.length); + /* exception: dedicated SBC */ + const [r] = await pp.query(sqlCarriersForAccountBySid, + [process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]); + if (0 === r.length) { + logger.debug( + `wasOriginatedFromCarrier: hosted jambonz (deicated sbc) no carriers for ${process.env.SBC_ACCOUNT_SID}`); + return failure; + } + return { + fromCarrier: true, + gateway: r[0], + account_sid: process.env.SBC_ACCOUNT_SID + }; + } + + if (!isDotDecimal) { + /* 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, req.source_address)); + if (selected) { + const [a] = await pp.query(sqlAccountByRealm, uri.host); + if (1 === a.length) { + logger.debug({ + realm: uri.host, + source_address: req.source_address, + account_sid: a[0].account_sid + }, 'wasOriginatedFromCarrier: (true) found carrier and account'); return { fromCarrier: true, - gateway, - account_sid: r[0].account_sid, - application_sid: r[0].application_sid, - account: accounts[0] + gateway: selected, + account_sid: a[0].account_sid, + application_sid: selected.application_sid, + account: a[0] }; } + } + if (process.env.JAMBONES_HOSTING) { + logger.debug('wasOriginatedFromCarrier: unknown realm (hosted jambonz'); return failure; } } - /* 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, req.source_address)); - if (selected) { - const [a] = await pp.query(sqlAccountByRealm, uri.host); - if (0 === a.length) return failure; + /* this is a self-hosted system (not a hosted jambonz system) + the req.uri is either an IP or a realm we did not find */ + /* check for a carrier at the service provider level with an inbound gw = source IP */ + const [gw] = await pp.query(sqlSelectAllGatewaysForSP); + const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address)); + if (matches.length) { + /* we have one or more carriers that match. Now we need to find one with a provisioned phone number */ + const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(','); + const did = normalizeDID(req.calledNumber); + const sql = `SELECT * FROM phone_numbers WHERE number = ${did} AND voip_carrier_sid IN (${vc_sids})`; + logger.debug({ + matches, sql, did, vc_sids + }, 'wasOriginatedFromCarrier: looking up DID for service provider carrier'); + + const [r] = await pp.query(sql); + if (0 === r.length) { + /* came from a carrier, but number is not provisioned.. + check if we only have a single account, otherwise we have no + way of knowing which account this is for + */ + const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?', + matches[0].service_provider_sid); + if (r[0].count !== 1) { + logger.debug('wasOriginatedFromCarrier: call from carrier associated with SP, no DID mapping though'); + return {fromCarrier: true}; + } + else { + const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?', + matches[0].service_provider_sid); + logger.debug('wasOriginatedFromCarrier: call from carrier associated with SP, with DID mapping'); + return { + fromCarrier: true, + gateway: matches[0], + account_sid: accounts[0].account_sid, + account: accounts[0] + }; + } + } + const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid); + const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid); + assert(accounts.length); return { fromCarrier: true, - gateway: selected, - account_sid: a[0].account_sid, - application_sid: selected.application_sid, - account: a[0] + gateway, + account_sid: r[0].account_sid, + application_sid: r[0].application_sid, + account: accounts[0] }; } + + /* not a service provider level carrier -- try account-level with inbound gw = source IP */ + const [gwAccount] = await pp.query(sqlSelectAllGatewaysForAccountBySourceAddress, req.source_address); + if (gwAccount.length) { + logger.debug({gwAccount}, 'wasOriginatedFromCarrier: call came from account level carrier'); + const accountSet = new Set(); + gwAccount.forEach((gw) => accountSet.add(gw.account_sid)); + if (accountSet.size > 1) { + logger.info({accounts: accountSet}, + `wasOriginatedFromCarrier: ${accountSet.size} accounts found with ${req.source_address} inbound gw`); + return failure; + } + const [accounts] = await pp.query('SELECT * from accounts where account_sid = ?', + gwAccount[0].account_sid); + logger.debug('wasOriginatedFromCarrier: call from carrier associated with account'); + return { + fromCarrier: true, + gateway: gwAccount[0], + account_sid: accounts[0].account_sid, + account: accounts[0] + }; + } + + logger.info(`wasOriginatedFromCarrier: no carrier found for host: ${req.uri} source: ${req.source_address}`); return failure; }; diff --git a/package.json b/package.json index 324216f..40987b1 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "scripts": { "start": "node app", - "test": "NODE_ENV=test JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ", + "test": "NODE_ENV=test JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ", "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test", "jslint": "eslint app.js lib" },