Compare commits

..
Author SHA1 Message Date
Quan HL 563ea405e9 wip 2023-10-24 12:56:07 +07:00
Quan HL 8cd91c19c9 support call to queue and application from registered user 2023-10-24 12:31:02 +07:00
Quan HL 994904e8e7 support call to queue and application from registered user 2023-10-24 12:27:12 +07:00
Quan HL 874d1804e1 support call to queue and application from registered user 2023-10-24 12:15:06 +07:00
Quan HL 9475b776be support device call to app 2023-10-23 14:14:26 +07:00
Hoan Luu Huu a3799576a2 add sentinelPassword option (#119) 2023-10-04 19:41:21 -04:00
Antony Jukesandajukes 63e72fbfce feat/MS Teams IP check improvement (#117)
* added ip in cidr utilities

* middleware add ms teams cidr check

* use cidr-matcher lib

* removed redundant require

* moved cidr-matcher require to top of file

* moved express require to top of file

---------

Co-authored-by: ajukes <ajukes@callable.io>
2023-09-18 10:35:04 -04:00
Dave Horton e5dce35bab more efficient checking of device calls (#118)
* more efficient checking of device calls

* when searching for matching gateways choose largest enclosing netmask
2023-09-15 10:53:22 -04:00
7 changed files with 120 additions and 28 deletions
+11 -3
View File
@@ -33,7 +33,10 @@ const JAMBONES_REDIS_SENTINELS = process.env.JAMBONES_REDIS_SENTINELS ? {
}),
...(process.env.JAMBONES_REDIS_SENTINEL_USERNAME && {
username: process.env.JAMBONES_REDIS_SENTINEL_USERNAME
})
}),
...(process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD && {
sentinelPassword: process.env.JAMBONES_REDIS_SENTINEL_SENTINAL_PASSWORD
}),
} : null;
const Srf = require('drachtio-srf');
@@ -64,6 +67,7 @@ const {LifeCycleEvents} = require('./lib/constants');
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = [];
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-sip`;
const Registrar = require('@jambonz/mw-registrar');
const {
pool,
@@ -77,7 +81,8 @@ const {
lookupAccountBySid,
lookupAccountCapacitiesBySid,
queryCallLimits,
lookupClientByAccountAndUsername
lookupClientByAccountAndUsername,
lookupAppBySid
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
port: process.env.JAMBONES_MYSQL_PORT || 3306,
@@ -97,6 +102,7 @@ const {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
const registrar = new Registrar(logger, redisClient);
const ngProtocol = process.env.JAMBONES_NG_PROTOCOL || 'udp';
const ngPort = process.env.RTPENGINE_PORT || ('udp' === ngProtocol ? 22222 : 8080);
@@ -117,6 +123,7 @@ srf.locals = {...srf.locals,
activeCallIds: new Map(),
getRtpEngine,
dbHelpers: {
registrar,
pool,
ping,
lookupAuthHook,
@@ -127,7 +134,8 @@ srf.locals = {...srf.locals,
lookupAccountBySipRealm,
lookupAccountCapacitiesBySid,
queryCallLimits,
lookupClientByAccountAndUsername
lookupClientByAccountAndUsername,
lookupAppBySid
},
realtimeDbHelpers: {
createSet,
+7 -1
View File
@@ -224,6 +224,12 @@ class CallSession extends Emitter {
if (this.req.locals.application_sid) {
Object.assign(headers, {'X-Application-Sid': this.req.locals.application_sid});
}
if (this.req.locals.queue_name) {
Object.assign(headers, {'X-Queue-Name': this.req.locals.queue_name});
}
if (this.req.locals.called_user) {
Object.assign(headers, {'X-Called-User': this.req.locals.called_user});
}
if (this.req.authorization) {
if (this.req.authorization.grant && this.req.authorization.grant.application_sid) {
Object.assign(headers, {'X-Application-Sid': this.req.authorization.grant.application_sid});
@@ -248,7 +254,7 @@ class CallSession extends Emitter {
'-Max-Forwards',
'-Record-Route',
'-Session-Expires',
'-X-Subspace-Forwarded-For'
'-X-Subspace-Forwarded-For',
],
proxyResponseHeaders: ['all', '-X-Trace-ID'],
localSdpB: spdOfferB,
+38 -18
View File
@@ -45,7 +45,7 @@ AND vc.is_active = 1
AND sg.inbound = 1
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ?';
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ? AND is_active = 1';
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
const sqlApplicationBySid = 'SELECT * from applications WHERE application_sid = ?';
@@ -153,24 +153,41 @@ module.exports = (srf, logger) => {
* Let's look for case #1 first...
*/
/* 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, logger, req.source_address));
if (selected) {
const [a] = await pp.query(sqlAccountByRealm, [uri.host]);
if (0 === a.length) return failure;
return {
fromCarrier: true,
gateway: selected,
service_provider_sid: a[0].service_provider_sid,
account_sid: a[0].account_sid,
application_sid: selected.application_sid,
account: a[0]
};
/* does anyone own this sip realm? */
const [a] = await pp.query(sqlAccountByRealm, [uri.host]);
if (a.length) {
assert(a.length === 1);
/* yes they do */
logger.debug(`sip realm is associated with account_sid: ${a[0].account_sid}`);
/**
* We have one of two cases:
* (1a). The user configured his or her carrier to send to their sip realm, or
* (1b). The user is making a call from a sip device.
*/
/* 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)
.sort((a, b) => b.netmask - a.netmask);
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
if (selected) {
return {
fromCarrier: true,
gateway: selected,
service_provider_sid: a[0].service_provider_sid,
account_sid: a[0].account_sid,
application_sid: selected.application_sid,
account: a[0]
};
}
return failure;
}
/* no match, so let's look for case #2 */
try {
logger.info({
@@ -178,7 +195,9 @@ module.exports = (srf, logger) => {
user: uri.user
}, 'sip realm is not associated with an account, checking carriers');
const [gw] = await pp.query(sqlSelectCarrierRequiringRegistration, [uri.host, uri.user]);
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
const matches = gw
.sort((a, b) => b.netmask - a.netmask)
.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address));
if (1 === matches.length) {
// bingo
//TODO: this assumes the carrier is associate to an account, not an SP
@@ -229,6 +248,7 @@ module.exports = (srf, logger) => {
/* find all carrier entries that have an inbound gateway matching the source IP */
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
let matches = gw
.sort((a, b) => b.netmask - a.netmask)
.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address))
.map((gw) => {
return {
+27 -4
View File
@@ -1,7 +1,7 @@
const debug = require('debug')('jambonz:sbc-inbound');
const assert = require('assert');
const parseUri = require('drachtio-srf').parseUri;
const {nudgeCallCounts, roundTripTime} = require('./utils');
const {nudgeCallCounts, roundTripTime, isMSTeamsCIDR} = require('./utils');
const digestChallenge = require('@jambonz/digest-utils');
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
@@ -28,7 +28,9 @@ module.exports = function(srf, logger) {
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid,
queryCallLimits
queryCallLimits,
lookupAppBySid,
registrar
} = srf.locals.dbHelpers;
const {stats, writeCdrs} = srf.locals;
@@ -155,7 +157,7 @@ module.exports = function(srf, logger) {
...req.locals
};
}
else if (msProxyIps.includes(req.source_address)) {
else if (msProxyIps.includes(req.source_address) || isMSTeamsCIDR(req.source_address)) {
logger.info({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
const uri = parseUri(req.uri);
@@ -196,11 +198,32 @@ module.exports = function(srf, logger) {
res.send(404);
return req.srf.endSession(req);
}
let deviceAppSid = null;
let called_user = null;
const queue_name = uri.user.startsWith('queue-') ? uri.user.match(/queue-(.*)/)[1] : null;
if (uri.user.startsWith('app-')) {
// Call from registered device to test application.
const appSid = uri.user.match(/app-(.*)/)[1];
const app = await lookupAppBySid(appSid);
if (app && app.account_sid === account.account_sid) {
deviceAppSid = app.application_sid;
}
} else if (!queue_name) {
// check if call to registered user
const {realm} = this.req.authorization.challengeResponse;
const calledAor = `${req.calledNumber}@${realm}`;
const reg = await registrar.query(calledAor);
if (reg) {
called_user = calledAor;
}
}
req.locals = {
service_provider_sid: account.service_provider_sid,
account_sid: account.account_sid,
account,
application_sid: account.device_calling_application_sid,
application_sid: deviceAppSid || account.device_calling_application_sid,
...(queue_name && ({queue_name})),
...(called_user && ({called_user})),
webhook_secret: account.webhook_secret,
realm: uri.host,
...(account.registration_hook && {
+19 -2
View File
@@ -1,5 +1,8 @@
const CIDRMatcher = require('cidr-matcher');
const express = require('express');
const rtpCharacteristics = require('../data/rtp-transcoding');
const srtpCharacteristics = require('../data/srtp-transcoding');
let idx = 0;
const isWSS = (req) => {
@@ -96,7 +99,6 @@ const handleErrors = (logger, app, resolve, reject, e) => {
const createHealthCheckApp = (port, logger) => {
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
@@ -178,6 +180,20 @@ const parseConnectionIp = (sdp) => {
return arr ? arr[1] : null;
};
/**
* Checks if ip is one of MS Teams sip signalling ips
* https://learn.microsoft.com/en-us/azure/communication-services/concepts
* /telephony/direct-routing-infrastructure#sip-signaling-fqdns
* @param ip IP address, example 172.31.0.1
* */
const isMSTeamsCIDR = (ip) => {
const cidrs = [
'52.112.0.0/14',
'52.120.0.0/14'
];
const matcher = new CIDRMatcher(cidrs);
return matcher.contains(ip);
};
module.exports = {
isWSS,
@@ -194,5 +210,6 @@ module.exports = {
createHealthCheckApp,
nudgeCallCounts,
roundTripTime,
parseConnectionIp
parseConnectionIp,
isMSTeamsCIDR
};
+17
View File
@@ -14,6 +14,7 @@
"@jambonz/db-helpers": "^0.9.1",
"@jambonz/digest-utils": "^0.0.3",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "^0.2.4",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.6",
@@ -1696,6 +1697,14 @@
"node": ">=14.x"
}
},
"node_modules/@jambonz/mw-registrar": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.5.tgz",
"integrity": "sha512-+aBI2xpR6Ir140Hi7/ED+z5Hl7NgCalyVzTLNlgUVzTvsMLtyZZh6n9IQipKnuKvhh4nPM4aJ+JuFhi1UH9zEA==",
"dependencies": {
"debug": "^4.3.4"
}
},
"node_modules/@jambonz/realtimedb-helpers": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
@@ -7664,6 +7673,14 @@
"express": "^4.17.2"
}
},
"@jambonz/mw-registrar": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.5.tgz",
"integrity": "sha512-+aBI2xpR6Ir140Hi7/ED+z5Hl7NgCalyVzTLNlgUVzTvsMLtyZZh6n9IQipKnuKvhh4nPM4aJ+JuFhi1UH9zEA==",
"requires": {
"debug": "^4.3.4"
}
},
"@jambonz/realtimedb-helpers": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
+1
View File
@@ -33,6 +33,7 @@
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.5",
"@jambonz/digest-utils": "^0.0.3",
"@jambonz/mw-registrar": "^0.2.4",
"@aws-sdk/client-sns": "^3.360.0",
"@aws-sdk/client-auto-scaling": "^3.360.0",
"bent": "^7.3.12",