Compare commits

..
26 Commits
Author SHA1 Message Date
Dave Horton 454986edcd #104 - pad crypto option (#105) 2023-09-15 13:53:35 -04:00
Dave Horton 114d34def4 fix for PAD_CRYPTO feature 2023-09-14 10:14:27 -04:00
Dave Horton 6cf59732fb add env var PAD_CRYPTO to pad crypto params (#102) 2023-08-30 22:09:28 -04:00
two56andMatt Preskett 7f2bce927d Fix: Feature server REFER (#90)
* Update _onFeatureServerTransfer to fix REFER case

* Avoid remote re-invite by using port latching

* Add INFO listener and pretend we're a uas

---------

Co-authored-by: Matt Preskett <matt.preskett@netcall.com>
2023-08-20 08:40:07 -04:00
Paulo Tellesandp.souza 41eaa4286f fix service_provider_sid undefined (#101)
Co-authored-by: p.souza <p.souza@cognigy.com>
2023-08-16 12:37:20 -04:00
Hoan Luu Huu 9fe731ab65 fix outbound sip uri To header and Request Line are not same (#99)
* fix outbound sip uri To header and Request Line are not same

* fix outbound sip uri To header and Request Line are not same
2023-08-09 07:47:52 -04:00
Dave Horton 0977dd16b3 remove X-Preferred-From-Host on outdial 2023-08-08 13:03:33 -04:00
Hoan Luu Huu 33971ed6d8 update stats collector version (#97) 2023-08-07 21:26:28 -04:00
Hoan Luu Huu 6200f2b50a feat: siprect custom headers (#95)
* feat: siprect custom headers

* update siprec client utils

* wip
2023-07-20 09:02:44 -04:00
Hoan Luu Huu 7e13c5559f feat: multi srs (#93) 2023-07-04 16:42:17 +01:00
Dave Horton 4343b01505 0.8.4 2023-06-28 09:33:32 +01:00
Hoan Luu Huu 81d7a9f1e6 feat: record all call (#84)
* feat: record all call

* feat: record all call

* feat: record all call

* feat: record all call

* add record format in recording url

* add record format to recording url

* fix ext
2023-06-09 15:01:46 -04:00
Dave Horton 695e44da29 Fix/trace (#92)
* fix docker build

* fix: trace_id in cdrs
2023-06-09 13:58:36 -04:00
Hoan Luu Huu 9b1a70d4a5 Merge pull request #91 from jambonz/feat/sentinel
Feat/sentinel
2023-06-08 21:17:37 +07:00
Quan HL db38269a77 fix 2023-06-07 21:13:08 +07:00
Quan HL 337c24df41 update 2023-06-07 20:42:26 +07:00
Quan HL 554cdbcacf feat: sentinel configuration 2023-06-07 15:16:06 +07:00
Hoan Luu Huu d50272d7da update dbhelper and redis cache (#86) 2023-05-29 10:21:03 -04:00
Dave Horton b2c4199c04 update siprec-client-utils 2023-05-29 09:22:45 -04:00
Hoan Luu Huu f79f29bd7e fix: remove X-Voip-Carrier-Sid for outbound INVITE (#88) 2023-05-29 09:21:23 -04:00
Hoan Luu Huu 738429a46f additional routing rule (#87)
* additional routing rule

* add testcase

* update failing testcase
2023-05-29 09:06:11 -04:00
Dave Horton 9015ac4e13 0.8.3 2023-05-11 09:25:44 -04:00
Dave Horton 750294eab2 add support for tls with srtp media encryption (#83) 2023-05-10 16:02:08 -04:00
Hoan Luu HuuandDave Horton 76558cc5c9 add protocol for outbound call (#82)
* add protocol for outbound call

* add protocol for outbound call

* update db-helper version

* lcr fixes

* fix cancel test

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
2023-05-10 14:43:46 -04:00
Dave Horton d604440907 update deps 2023-05-08 13:13:49 -04:00
Hoan Luu Huu 039a60c1b7 feat: lcr added (#80)
* feat: lcr added

* fix: await for async

* fix: add lookupCarrierByAccountLcr to locals

* fix typo

* fix typo

* fix typo
2023-05-08 09:06:37 -04:00
14 changed files with 549 additions and 273 deletions
+3 -1
View File
@@ -2,6 +2,8 @@ name: Docker
on:
push:
branches:
- main
tags:
- '*'
@@ -18,7 +20,7 @@ jobs:
- name: prepare tag
id: prepare_tag
run: |
IMAGE_ID=$GITHUB_REPOSITORY
IMAGE_ID=jambonz/sbc-outbound
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
+32 -9
View File
@@ -3,12 +3,38 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_USER &&
process.env.JAMBONES_MYSQL_PASSWORD &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
if (process.env.JAMBONES_REDIS_SENTINELS) {
assert.ok(process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
'missing JAMBONES_REDIS_SENTINEL_MASTER_NAME env var, JAMBONES_REDIS_SENTINEL_PASSWORD env var is optional');
} else {
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
}
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR env var');
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
const JAMBONES_REDIS_SENTINELS = process.env.JAMBONES_REDIS_SENTINELS ? {
sentinels: process.env.JAMBONES_REDIS_SENTINELS.split(',').map((sentinel) => {
let host, port = 26379;
if (sentinel.includes(':')) {
const arr = sentinel.split(':');
host = arr[0];
port = parseInt(arr[1], 10);
} else {
host = sentinel;
}
return {host, port};
}),
name: process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
...(process.env.JAMBONES_REDIS_SENTINEL_PASSWORD && {
password: process.env.JAMBONES_REDIS_SENTINEL_PASSWORD
}),
...(process.env.JAMBONES_REDIS_SENTINEL_USERNAME && {
username: process.env.JAMBONES_REDIS_SENTINEL_USERNAME
})
} : null;
const Srf = require('drachtio-srf');
const srf = new Srf('sbc-outbound');
const CIDRMatcher = require('cidr-matcher');
@@ -37,7 +63,7 @@ const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp
const rtpServers = [];
const {
ping,
performLcr,
lookupOutboundCarrierForAccount,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
@@ -61,8 +87,8 @@ const {
incrKey,
decrKey,
retrieveSet,
isMemberOfSet
} = require('@jambonz/realtimedb-helpers')({
isMemberOfSet,
} = require('@jambonz/realtimedb-helpers')(JAMBONES_REDIS_SENTINELS || {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
@@ -84,7 +110,7 @@ srf.locals = {...srf.locals,
idleEmitter,
dbHelpers: {
ping,
performLcr,
lookupOutboundCarrierForAccount,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
@@ -102,10 +128,7 @@ srf.locals = {...srf.locals,
isMemberOfSet
}
};
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
});
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, redisClient);
const ngProtocol = process.env.JAMBONES_NG_PROTOCOL || 'udp';
const ngPort = process.env.RTPENGINE_PORT || ('udp' === ngProtocol ? 22222 : 8080);
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
+151 -100
View File
@@ -33,19 +33,21 @@ const createBLegToHeader = (req, teams) => {
return `sip:anonymous@${host}`;
};
const initCdr = (srf, req) => {
const uri = parseUri(req.uri);
const initCdr = (req, invite) => {
const {srf} = req;
const {trace_id} = req.locals;
const uri = parseUri(invite.uri);
const regex = /^\+(\d+)$/;
let arr = regex.exec(req.calledNumber);
const to = arr ? arr[1] : req.calledNumber;
arr = regex.exec(req.callingNumber);
const from = arr ? arr[1] : req.callingNumber;
let arr = regex.exec(invite.calledNumber);
const to = arr ? arr[1] : invite.calledNumber;
arr = regex.exec(invite.callingNumber);
const from = arr ? arr[1] : invite.callingNumber;
const applicationSid = req.get('X-Application-Sid');
return {
account_sid: req.get('X-Account-Sid'),
call_sid: req.get('X-Call-Sid'),
sip_callid: req.get('Call-ID'),
sip_callid: invite.get('Call-ID'),
...(applicationSid && {application_sid: applicationSid}),
from,
to,
@@ -55,7 +57,7 @@ const initCdr = (srf, req) => {
direction: 'outbound',
host: srf.locals.sipAddress,
remote_host: uri.host,
trace_id: req.get('X-Trace-ID') || '00000000000000000000000000000000'
trace_id: trace_id || '00000000000000000000000000000000'
};
};
@@ -83,12 +85,13 @@ class CallSession extends Emitter {
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
const {performLcr,
const {
lookupOutboundCarrierForAccount,
lookupCarrierBySid,
lookupSipGatewaysByCarrier,
lookupCarrierByAccountLcr
} = this.srf.locals.dbHelpers;
this.performLcr = performLcr;
this.lookupOutboundCarrierForAccount = lookupOutboundCarrierForAccount;
this.lookupCarrierBySid = lookupCarrierBySid;
this.lookupSipGatewaysByCarrier = lookupSipGatewaysByCarrier;
this.lookupCarrierByAccountLcr = lookupCarrierByAccountLcr;
@@ -96,6 +99,10 @@ class CallSession extends Emitter {
this._mediaReleased = false;
}
get service_provider_sid() {
return this.req.locals.service_provider_sid;
}
get account_sid() {
return this.req.locals.account_sid;
}
@@ -168,8 +175,9 @@ class CallSession extends Emitter {
this.subscribeRequest = subscribeRequest;
this.subscribeAnswer = subscribeAnswer;
this.unsubscribe = unsubscribe;
this.srsClients = [];
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, this.useWss || teams, teams);
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, this.useWss || teams, false, teams);
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
let proxy, uris;
const mapGateways = new Map();
@@ -222,60 +230,77 @@ class CallSession extends Emitter {
let voip_carrier_sid = this.req.get('X-Requested-Carrier-Sid');
const account_sid = this.req.get('X-Account-Sid');
if (!voip_carrier_sid && account_sid) {
debug('calling lcr');
/* search for an LCR table for this account or service provider */
voip_carrier_sid = await this.lookupCarrierByAccountLcr(account_sid, this.req.calledNumber);
}
if (voip_carrier_sid) {
const vc = await this.lookupCarrierBySid(voip_carrier_sid);
const gateways = await this.lookupSipGatewaysByCarrier(voip_carrier_sid);
const gws = (gateways || [])
.filter((gw) => gw.outbound);
if (gws.length) {
uris = [];
gws.forEach((o) => {
const prefix = vc.tech_prefix;
const hostport = !o.port || 5060 === o.port ? o.ipv4 : `${o.ipv4}:${o.port}`;
const prependPlus = vc.e164_leading_plus && !this.req.calledNumber.startsWith('0');
const transport = o.protocol || 'udp';
const u = `sip:${prefix ? prefix : ''}${prependPlus ? '+' : ''}${this.req.calledNumber}@${hostport};transport=${transport}`;
const obj = {
name: vc.name,
diversion: vc.diversion,
hostport
};
if (vc.register_username && vc.register_password) {
obj.auth = {
username: vc.register_username,
password: vc.register_password
};
}
mapGateways.set(u, obj);
uris.push(u);
});
this.logger.debug({uris, voip_carrier_sid}, 'selected outbound gateways for requested carrier');
}
else {
this.logger.info({voip_carrier_sid}, 'no outbound gateways found for requested carrier');
if (!voip_carrier_sid) {
/* no LCR for this account/SP - try with inbound carrier */
const inbound_carrier_sid = this.req.get('X-Voip-Carrier-Sid');
if (inbound_carrier_sid) {
const gateways = await this.lookupSipGatewaysByCarrier(inbound_carrier_sid);
const gws = (gateways || [])
.filter((gw) => gw.outbound);
if (gws.length) {
voip_carrier_sid = inbound_carrier_sid;
}
}
}
if (mapGateways.size === 0) {
/**
* We normalize the called number by removing a leading + before sending it to LCR..
* but LCR will return us an array of sip uris, with leading + for carriers that require it
*/
const routableNumber = this.req.calledNumber.startsWith('+') ?
this.req.calledNumber.slice(1) :
this.req.calledNumber;
const gateways = await this.performLcr(routableNumber, this.account_sid);
if (!gateways || gateways.length === 0) throw new Error('no routes found');
debug(`got gateways: ${JSON.stringify(gateways)}`);
gateways.forEach((gw) => mapGateways.set(gw.uri, {
name: gw.name,
auth: gw.auth,
diversion: gw.diversion,
hostport: gw.hostport
}));
uris = gateways.map((gw) => gw.uri);
if (!voip_carrier_sid) {
/* no LCR/ inbound carrier for this account/SP - at this point its a random shuffle of outbound carriers */
voip_carrier_sid = await this.lookupOutboundCarrierForAccount(this.account_sid);
}
if (!voip_carrier_sid) {
/* no outbound carriers exist for this account/SP */
this.logger.info(`no outbound carriers found for account_sid ${account_sid}`);
this.res.send(603);
return this.srf.endSession(this.req);
}
const vc = await this.lookupCarrierBySid(voip_carrier_sid);
const gateways = await this.lookupSipGatewaysByCarrier(voip_carrier_sid);
const gws = (gateways || [])
.filter((gw) => gw.outbound);
if (gws.length) {
uris = [];
gws.forEach((o) => {
const calledNumber = this.req.calledNumber.startsWith('+') ?
this.req.calledNumber.slice(1) :
this.req.calledNumber;
const prefix = vc.tech_prefix || '';
const protocol = o.protocol?.startsWith('tls') ? 'tls' : (o.protocol || 'udp');
const hostport = !o.port || 5060 === o.port ? o.ipv4 : `${o.ipv4}:${o.port}`;
const prependPlus = vc.e164_leading_plus && !this.req.calledNumber.startsWith('0') ? '+' : '';
const transport = `transport=${protocol}`;
const scheme = protocol === 'tls' ? 'sips' : 'sip';
const u = `${scheme}:${prefix}${prependPlus}${calledNumber}@${hostport};${transport}`;
const obj = {
name: vc.name,
diversion: vc.diversion,
hostport,
protocol: o.protocol
};
if (vc.register_username && vc.register_password) {
obj.auth = {
username: vc.register_username,
password: vc.register_password
};
}
mapGateways.set(u, obj);
uris.push(u);
if (o.protocol === 'tls/srtp') {
/** TODO: this is a bit of a hack in the sense that we are not
* supporting a scenario where you have a carrier with several outbound
* gateways, some requiring encrypted media and some not. This should be rectified
* but it will require more significant changes and right now it seems
* like a rare use case -- encryption is usually an all or nothing requirement.
*/
this.logger.info({u}, `using SRTP for outbound call, pad crypto: ${o.pad_crypto ? 'yes' : 'no'}`);
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, true, o.pad_crypto, true);
}
});
this.logger.debug({uris, voip_carrier_sid}, 'selected outbound gateways for requested carrier');
}
else {
this.logger.info({voip_carrier_sid}, 'no outbound gateways found for requested carrier');
}
} catch (err) {
debug(err);
@@ -327,9 +352,10 @@ class CallSession extends Emitter {
this.logger.info({err}, 'Error retrieving iip key');
}
}
// INVITE request line and To header should be the same.
hdrs = {...hdrs, 'To': uri};
if (gw) {
this.logger.info({gw}, `sending INVITE to ${uri} via carrier ${gw.name}`);
hdrs = {...hdrs, 'To': uri};
if (gw.diversion) {
let div = gw.diversion;
if (div.startsWith('+')) {
@@ -360,8 +386,9 @@ class CallSession extends Emitter {
'-X-Jambonz-Routing',
'-X-Jambonz-FS-UUID',
'-X-Preferred-From-User',
'X-Preferred-From-Host',
'-X-Preferred-From-Host',
'-X-Jambonz-FS-UUID',
'-X-Voip-Carrier-Sid',
],
proxyResponseHeaders: [
'all',
@@ -405,7 +432,7 @@ class CallSession extends Emitter {
}
else {
this.req.locals.cdr = {
...initCdr(this.req.srf, inv),
...initCdr(this.req, inv),
service_provider_sid: this.req.locals.service_provider_sid,
account_sid: this.req.locals.account_sid,
...(this.req.locals.application_sid && {application_sid: this.req.locals.application_sid}),
@@ -521,12 +548,18 @@ class CallSession extends Emitter {
/* write cdr for connected call */
if (this.req.locals.cdr) {
const now = Date.now();
const day = new Date();
const recordAllCalls = this.req.locals.record_all_calls;
let recording_url = `/Accounts/${this.account_sid}/RecentCalls/${this.req.locals.cdr.call_sid}/record`;
recording_url += `/${day.getFullYear()}/${(day.getMonth() + 1).toString().padStart(2, '0')}`;
recording_url += `/${day.getDate().toString().padStart(2, '0')}/${recordAllCalls}`;
this.writeCdrs({...this.req.locals.cdr,
terminated_at: now,
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
sip_status: 200,
answered: true,
duration: Math.floor((now - callStart) / 1000)
duration: Math.floor((now - callStart) / 1000),
...(recordAllCalls && {recording_url})
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
}
/* de-link the 2 Dialogs for GC */
@@ -535,10 +568,7 @@ class CallSession extends Emitter {
dlg.other = null;
other.other = null;
if (this.srsClient) {
this.srsClient.stop();
this.srsClient = null;
}
this._stopRecording();
this.logger.info(`call ended with normal termination, there are ${this.activeCallIds.size} active`);
if (this.activeCallIds.size === 0) this.idleEmitter.emit('idle');
@@ -563,6 +593,13 @@ class CallSession extends Emitter {
forwardInDialogRequests(uac, ['notify', 'options', 'message']);
}
_stopRecording() {
if (this.srsClients.length) {
this.srsClients.forEach((c) => c.stop());
this.srsClients = [];
}
}
async _onRefer(dlg, req, res) {
/* REFER coming in from a sip device, forward to feature server */
try {
@@ -701,6 +738,7 @@ Duration=${payload.duration} `
const to = this.req.getParsedHeader('To');
const aorFrom = from.uri;
const aorTo = to.uri;
const headers = contentType === 'application/json' && req.body ? JSON.parse(req.body) : {};
this.logger.info({to, from}, 'startCallRecording request for an outbound call');
const srsUrl = req.get('X-Srs-Url');
@@ -708,7 +746,7 @@ Duration=${payload.duration} `
const callSid = req.get('X-Call-Sid');
const accountSid = req.get('X-Account-Sid');
const applicationSid = req.get('X-Application-Sid');
if (this.srsClient) {
if (this.srsClients.length) {
res.send(400);
this.logger.info('discarding duplicate startCallRecording request for a call');
return;
@@ -718,13 +756,14 @@ Duration=${payload.duration} `
res.send(400);
return;
}
this.srsClient = new SrsClient(this.logger, {
const arr = srsUrl.split(',');
this.srsClients = arr.map((url) => new SrsClient(this.logger, {
srf: dlg.srf,
direction: 'outbound',
originalInvite: this.req,
callingNumber: this.req.callingNumber,
calledNumber: this.req.calledNumber,
srsUrl,
srsUrl: url,
srsRecordingId,
callSid,
accountSid,
@@ -738,42 +777,51 @@ Duration=${payload.duration} `
del: this.del,
blockMedia: this.blockMedia,
unblockMedia: this.unblockMedia,
unsubscribe: this.unsubscribe
});
unsubscribe: this.unsubscribe,
headers
}));
try {
succeeded = await this.srsClient.start();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.start())
)).every((r) => r);
} catch (err) {
this.logger.error({err}, 'Error starting SipRec call recording');
}
}
else if (reason === 'stopCallRecording') {
if (!this.srsClient) {
if (!this.srsClients.length) {
res.send(400);
this.logger.info('discarding stopCallRecording request because we are not recording');
return;
}
try {
succeeded = await this.srsClient.stop();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.stop())
)).every((r) => r);
} catch (err) {
this.logger.error({err}, 'Error stopping SipRec call recording');
}
this.srsClient = null;
this.srsClients = [];
}
else if (reason === 'pauseCallRecording') {
if (!this.srsClient || this.srsClient.paused) {
if (!this.srsClients.length || this.srsClients.every((c) => c.paused)) {
this.logger.info('discarding invalid pauseCallRecording request');
res.send(400);
return;
}
succeeded = await this.srsClient.pause();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.pause())
)).every((r) => r);
}
else if (reason === 'resumeCallRecording') {
if (!this.srsClient || !this.srsClient.paused) {
if (!this.srsClients.length || !this.srsClients.every((c) => c.paused)) {
res.send(400);
this.logger.info('discarding invalid resumeCallRecording request');
return;
}
succeeded = await this.srsClient.resume();
succeeded = (await Promise.all(
this.srsClients.map((c) => c.resume())
)).every((r) => r);
}
res.send(succeeded ? 200 : 503);
}
@@ -859,35 +907,38 @@ Duration=${payload.duration} `
...(req.has('X-Retain-Call-Sid') && {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')}),
...(req.has('X-Account-Sid') && {'X-Account-Sid': req.get('X-Account-Sid')})
};
const dlg = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
this.uas = dlg;
this.uas.other = this.uac;
this.uac.other = this.uas;
this.uas.on('modify', this._onReinvite.bind(this, this.uas));
this.uas.on('refer', this._onFeatureServerTransfer.bind(this, this.uas));
this.uas.on('destroy', () => {
const uac = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
this.uas = uac;
uac.type = 'uas';
uac.other = this.uac;
this.uac.other = uac;
uac.on('info', this._onInfo.bind(this, uac));
uac.on('modify', this._onReinvite.bind(this, uac));
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
uac.on('destroy', () => {
this.logger.info('call ended with normal termination');
this.rtpEngineResource.destroy();
this.activeCallIds.delete(this.req.get('Call-ID'));
if (this.activeCallIds.size === 0) this.idleEmitter.emit('idle');
this.uas.other.destroy();
uac.other.destroy();
this.srf.endSession(this.req);
});
// modify rtpengine to stream to new feature server
let response = await this.offer(Object.assign(this.rtpEngineOpts.offer, {sdp: this.uas.remote.sdp}));
const opts = {
...this.rtpEngineOpts.common,
'from-tag': this.rtpEngineOpts.uas.tag,
sdp: uac.remote.sdp,
flags: ['port latching']
};
const response = await this.offer(opts);
if ('ok' !== response.result) {
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
}
const sdp = await this.uas.other.modify(response.sdp);
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
this.rtpEngineOpts.answer);
response = await this.answer(opts);
if ('ok' !== response.result) {
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
throw new Error(`_onFeatureServerTransfer: rtpengine offer failed: ${JSON.stringify(response)}`);
}
dlg.destroy().catch(() => {});
this.logger.info('successfully moved call to new feature server');
} catch (err) {
res.send(488);
this.logger.error(err, 'Error handling refer from feature server');
}
}
+3 -2
View File
@@ -4,10 +4,10 @@ const Registrar = require('@jambonz/mw-registrar');
const {selectHostPort, nudgeCallCounts} = require('./utils');
const FS_UUID_SET_NAME = 'fsUUIDs';
module.exports = (srf, logger, opts) => {
module.exports = (srf, logger, redisClient) => {
const {incrKey, decrKey, isMemberOfSet} = srf.locals.realtimeDbHelpers;
const {stats} = srf.locals;
const registrar = new Registrar(opts);
const registrar = new Registrar(logger, redisClient);
const {
lookupAccountCapacitiesBySid,
lookupAccountBySid,
@@ -19,6 +19,7 @@ module.exports = (srf, logger, opts) => {
const callId = req.get('Call-ID');
req.locals.account_sid = req.get('X-Account-Sid');
req.locals.application_sid = req.get('X-Application-Sid');
req.locals.record_all_calls = req.get('X-Record-All-Calls');
const traceId = req.locals.trace_id = req.get('X-Trace-ID');
req.locals.logger = logger.child({
callId,
+7 -1
View File
@@ -2,10 +2,16 @@ const rtpCharacteristics = require('../data/rtp-transcoding');
const srtpCharacteristics = require('../data/srtp-transcoding');
const debug = require('debug')('jambonz:sbc-outbound');
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams) {
const from = req.getParsedHeader('from');
const rtpCopy = JSON.parse(JSON.stringify(rtpCharacteristics));
const srtpCopy = JSON.parse(JSON.stringify(srtpCharacteristics));
if (padCrypto) {
srtpCopy['default'].flags.push('SDES-pad');
srtpCopy['teams'].flags.push('SDES-pad');
}
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
+133 -120
View File
@@ -1,21 +1,21 @@
{
"name": "sbc-outbound",
"version": "v0.8.2",
"version": "0.8.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "sbc-outbound",
"version": "v0.8.2",
"version": "0.8.4",
"license": "MIT",
"dependencies": {
"@jambonz/db-helpers": "^0.7.8",
"@jambonz/db-helpers": "^0.9.0",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "0.2.2",
"@jambonz/realtimedb-helpers": "^0.7.0",
"@jambonz/mw-registrar": "0.2.4",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.4",
"@jambonz/stats-collector": "^0.1.8",
"@jambonz/siprec-client-utils": "^0.2.6",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.5",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.4",
@@ -538,6 +538,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -573,9 +578,9 @@
}
},
"node_modules/@jambonz/db-helpers": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.7.9.tgz",
"integrity": "sha512-JajIJSPXW0OTjVprDwTH8/yU/qhHY1XkXe6f5PRh0kvRt42AmYggcGKqgivUkY1seR/GVCnQccDcIA2ZEdvDUg==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.0.tgz",
"integrity": "sha512-4fvwONj4jQNIHyG76WGdE7AuMt9vDl4sfHmHrY3PSgOh+kf2BCtBEYqoxyJ96/NH9OeUYkuXoM6fjSvpS7GYVw==",
"dependencies": {
"cidr-matcher": "^2.1.1",
"debug": "^4.3.4",
@@ -596,34 +601,20 @@
}
},
"node_modules/@jambonz/mw-registrar": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.2.tgz",
"integrity": "sha512-CG0MUVRZZ+tBgB5kvjn5IS1R/OdNV5PpAsxCKE0ehe6XVJ1ap4ch9hSZmEh2q7qPnWu8sR451peF33dnoSeaPA==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.4.tgz",
"integrity": "sha512-GHRlvHsDvwfQfqOUKh4Emt+CvoiODQ+Tny5zg8ZT5q5yz77qy3XIfOF072xO9gDSPrXjn0rH13W3iIKtNzcOIA==",
"dependencies": {
"@jambonz/promisify-redis": "^0.0.6",
"debug": "^4.3.1",
"redis": "^3.1.1"
}
},
"node_modules/@jambonz/promisify-redis": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@jambonz/promisify-redis/-/promisify-redis-0.0.6.tgz",
"integrity": "sha512-9KmWV+ODDOPwdqijhgXOXkloGNm7nmCf3ch4D1vN46lh9FQLmnlQmEmBHIFaKB3vAgIgcMzNzcNmY23JGCGguA==",
"dependencies": {
"redis-commands": "^1.6.0"
},
"peerDependencies": {
"redis": "^3.0.0"
"debug": "^4.3.1"
}
},
"node_modules/@jambonz/realtimedb-helpers": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.7.0.tgz",
"integrity": "sha512-hu+CcV0jzQU6J4vW8bBEz52NqEroLHOB0wjVb/VmSL4N0+MAJ1wbPUNk8go5aMk809Vr/Od2z9nHInGnTiBHzA==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
"integrity": "sha512-ODKVVat2VAQheY13DjiAxiBBOmffMoeu3xviTjsTODr3ySZkqQZvSWU7N80pAIZvoiWG1UUEy9yI8D8mE4Jgiw==",
"dependencies": {
"@jambonz/promisify-redis": "^0.0.6",
"debug": "^4.3.4",
"redis": "^3.1.2"
"ioredis": "^5.3.2"
}
},
"node_modules/@jambonz/rtpengine-utils": {
@@ -637,18 +628,18 @@
}
},
"node_modules/@jambonz/siprec-client-utils": {
"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==",
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.6.tgz",
"integrity": "sha512-z2x6nghLaCfOBPcr36f+1vxsV557X3z5JW1L1RxZg8YPWkTboNIHyHxtEcAua7V9g6EB4G1qv2KDwA+iE5n5Ww==",
"dependencies": {
"sdp-transform": "^2.14.1",
"uuid": "^8.3.2"
}
},
"node_modules/@jambonz/stats-collector": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/@jambonz/stats-collector/-/stats-collector-0.1.8.tgz",
"integrity": "sha512-PadBKTmcphU7bZI7pVr4awhoIi+DMLw7An6AGE0GGsLCn2x0IGnxr9k47gI4Bk3OaSF6N7RyVLEAMkJfr24l8Q==",
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/@jambonz/stats-collector/-/stats-collector-0.1.9.tgz",
"integrity": "sha512-JNRBaHQ47pWsXydj4gUp7zc64/0pM89a6E9pA8uQ15l1KxPGYYTrNRdone5aJqLTFOoPl3tYeF1kXj+3nU1nEA==",
"dependencies": {
"debug": "^4.3.2",
"hot-shots": "^8.5.0"
@@ -1116,6 +1107,14 @@
"node": ">=6"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2477,6 +2476,29 @@
"node": ">= 0.4"
}
},
"node_modules/ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ip6addr": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/ip6addr/-/ip6addr-0.2.5.tgz",
@@ -3017,12 +3039,22 @@
"node": ">=8"
}
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.flattendeep": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
"dev": true
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3756,29 +3788,6 @@
"node": ">= 12.13.0"
}
},
"node_modules/redis": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"dependencies": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-redis"
}
},
"node_modules/redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
@@ -3798,14 +3807,6 @@
"node": ">=4"
}
},
"node_modules/redis/node_modules/denque": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
@@ -4226,6 +4227,11 @@
"node": ">= 0.6"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -5207,6 +5213,11 @@
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -5235,9 +5246,9 @@
"dev": true
},
"@jambonz/db-helpers": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.7.9.tgz",
"integrity": "sha512-JajIJSPXW0OTjVprDwTH8/yU/qhHY1XkXe6f5PRh0kvRt42AmYggcGKqgivUkY1seR/GVCnQccDcIA2ZEdvDUg==",
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@jambonz/db-helpers/-/db-helpers-0.9.0.tgz",
"integrity": "sha512-4fvwONj4jQNIHyG76WGdE7AuMt9vDl4sfHmHrY3PSgOh+kf2BCtBEYqoxyJ96/NH9OeUYkuXoM6fjSvpS7GYVw==",
"requires": {
"cidr-matcher": "^2.1.1",
"debug": "^4.3.4",
@@ -5255,31 +5266,20 @@
}
},
"@jambonz/mw-registrar": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.2.tgz",
"integrity": "sha512-CG0MUVRZZ+tBgB5kvjn5IS1R/OdNV5PpAsxCKE0ehe6XVJ1ap4ch9hSZmEh2q7qPnWu8sR451peF33dnoSeaPA==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@jambonz/mw-registrar/-/mw-registrar-0.2.4.tgz",
"integrity": "sha512-GHRlvHsDvwfQfqOUKh4Emt+CvoiODQ+Tny5zg8ZT5q5yz77qy3XIfOF072xO9gDSPrXjn0rH13W3iIKtNzcOIA==",
"requires": {
"@jambonz/promisify-redis": "^0.0.6",
"debug": "^4.3.1",
"redis": "^3.1.1"
}
},
"@jambonz/promisify-redis": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@jambonz/promisify-redis/-/promisify-redis-0.0.6.tgz",
"integrity": "sha512-9KmWV+ODDOPwdqijhgXOXkloGNm7nmCf3ch4D1vN46lh9FQLmnlQmEmBHIFaKB3vAgIgcMzNzcNmY23JGCGguA==",
"requires": {
"redis-commands": "^1.6.0"
"debug": "^4.3.1"
}
},
"@jambonz/realtimedb-helpers": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.7.0.tgz",
"integrity": "sha512-hu+CcV0jzQU6J4vW8bBEz52NqEroLHOB0wjVb/VmSL4N0+MAJ1wbPUNk8go5aMk809Vr/Od2z9nHInGnTiBHzA==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@jambonz/realtimedb-helpers/-/realtimedb-helpers-0.8.6.tgz",
"integrity": "sha512-ODKVVat2VAQheY13DjiAxiBBOmffMoeu3xviTjsTODr3ySZkqQZvSWU7N80pAIZvoiWG1UUEy9yI8D8mE4Jgiw==",
"requires": {
"@jambonz/promisify-redis": "^0.0.6",
"debug": "^4.3.4",
"redis": "^3.1.2"
"ioredis": "^5.3.2"
}
},
"@jambonz/rtpengine-utils": {
@@ -5293,18 +5293,18 @@
}
},
"@jambonz/siprec-client-utils": {
"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==",
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@jambonz/siprec-client-utils/-/siprec-client-utils-0.2.6.tgz",
"integrity": "sha512-z2x6nghLaCfOBPcr36f+1vxsV557X3z5JW1L1RxZg8YPWkTboNIHyHxtEcAua7V9g6EB4G1qv2KDwA+iE5n5Ww==",
"requires": {
"sdp-transform": "^2.14.1",
"uuid": "^8.3.2"
}
},
"@jambonz/stats-collector": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/@jambonz/stats-collector/-/stats-collector-0.1.8.tgz",
"integrity": "sha512-PadBKTmcphU7bZI7pVr4awhoIi+DMLw7An6AGE0GGsLCn2x0IGnxr9k47gI4Bk3OaSF6N7RyVLEAMkJfr24l8Q==",
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/@jambonz/stats-collector/-/stats-collector-0.1.9.tgz",
"integrity": "sha512-JNRBaHQ47pWsXydj4gUp7zc64/0pM89a6E9pA8uQ15l1KxPGYYTrNRdone5aJqLTFOoPl3tYeF1kXj+3nU1nEA==",
"requires": {
"debug": "^4.3.2",
"hot-shots": "^8.5.0"
@@ -5657,6 +5657,11 @@
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"dev": true
},
"cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -6707,6 +6712,22 @@
"side-channel": "^1.0.4"
}
},
"ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"requires": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
}
},
"ip6addr": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/ip6addr/-/ip6addr-0.2.5.tgz",
@@ -7093,12 +7114,22 @@
"p-locate": "^4.1.0"
}
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"lodash.flattendeep": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
"dev": true
},
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -7669,29 +7700,6 @@
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz",
"integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg=="
},
"redis": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
"requires": {
"denque": "^1.5.0",
"redis-commands": "^1.7.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
},
"dependencies": {
"denque": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
}
}
},
"redis-commands": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
},
"redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
@@ -8016,6 +8024,11 @@
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="
},
"standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-outbound",
"version": "v0.8.2",
"version": "0.8.4",
"main": "app.js",
"engines": {
"node": ">= 12.0.0"
@@ -27,13 +27,13 @@
"jslint": "eslint app.js lib"
},
"dependencies": {
"@jambonz/db-helpers": "^0.7.8",
"@jambonz/realtimedb-helpers": "^0.7.0",
"@jambonz/db-helpers": "^0.9.0",
"@jambonz/realtimedb-helpers": "^0.8.6",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "0.2.2",
"@jambonz/mw-registrar": "0.2.4",
"@jambonz/rtpengine-utils": "^0.4.3",
"@jambonz/siprec-client-utils": "^0.2.4",
"@jambonz/stats-collector": "^0.1.8",
"@jambonz/siprec-client-utils": "^0.2.6",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.5",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.4",
+2 -2
View File
@@ -429,7 +429,7 @@ inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound ca
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
voip_carrier_sid CHAR(36) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT 1,
protocol ENUM('udp','tcp','tls') DEFAULT 'udp' COMMENT 'Outbound call protocol',
protocol ENUM('udp','tcp','tls', 'tls/srtp') DEFAULT 'udp' COMMENT 'Outbound call protocol',
PRIMARY KEY (sip_gateway_sid)
) COMMENT='A whitelisted sip gateway used for origination/termination';
@@ -683,4 +683,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1;
SET FOREIGN_KEY_CHECKS=1;
+16 -5
View File
@@ -27,12 +27,23 @@ insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('e71519ff-4494-4c98-a06a-324e2712d94b', '1d8ef351-062a-4487-94f8-7698d5a40d24', '172.39.0.21', true, true);
-- lcr route: first try eastco (503) then westco (200)
insert into lcr(lcr_sid, name, is_active, service_provider_sid) values ('0eba4204-b036-4388-8f47-724c4cfb3d4f', 'default LCR', 1, '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into lcr_routes(lcr_route_sid, lcr_sid, regex, priority) values ('0eba4204-b036-4388-8f47-724c4cfb3d4e', '0eba4204-b036-4388-8f47-724c4cfb3d4f', '.*', 1);
-- lcr route: default eastco, but 1617 matches westco (503)
insert into lcr(lcr_sid, account_sid)
values('c0b0b6a0-0b0a-4b0b-8b0b-0b0b0b0b0b0b', 'ed649e33-e771-403a-8c99-1780eabbc803');
insert into lcr_routes(lcr_route_sid, lcr_sid, regex, priority)
values ('9eba4204-b036-4388-8f47-724c4cfb3d4e', 'c0b0b6a0-0b0a-4b0b-8b0b-0b0b0b0b0b0b', '.*', 999);
insert into lcr_carrier_set_entry(lcr_carrier_set_entry_sid, lcr_route_sid, voip_carrier_sid, priority)
values ('b677a7b5-bec6-4045-ae4a-a67a5ccb3448', '0eba4204-b036-4388-8f47-724c4cfb3d4e', '1d8ef351-062a-4487-94f8-7698d5a40d24', 2);
values ('13e344a0-8a4c-4f95-8a19-ccbfc7ab053e', '9eba4204-b036-4388-8f47-724c4cfb3d4e', '1d8ef351-062a-4487-94f8-7698d5a40d24', 1);
-- attach default route to lcr
update lcr set default_carrier_set_entry_sid = '13e344a0-8a4c-4f95-8a19-ccbfc7ab053e';
-- add a route based on digit match
insert into lcr_routes(lcr_route_sid, lcr_sid, regex, priority)
values ('3eba4204-b036-4388-8f47-724c4cfb3d4e', 'c0b0b6a0-0b0a-4b0b-8b0b-0b0b0b0b0b0b', '1617', 1);
insert into lcr_carrier_set_entry(lcr_carrier_set_entry_sid, lcr_route_sid, voip_carrier_sid, priority)
values ('13e344a0-8a4c-4f95-8a19-ccbfc7ab053e', '0eba4204-b036-4388-8f47-724c4cfb3d4e', '287c1452-620d-4195-9f19-c9814ef90d78', 1);
values ('b677a7b5-bec6-4045-ae4a-a67a5ccb3448', '3eba4204-b036-4388-8f47-724c4cfb3d4e', '287c1452-620d-4195-9f19-c9814ef90d78', 1);
-22
View File
@@ -17,29 +17,7 @@ values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabb
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
-- "good" carrier - "westco" at 172.39.0.20
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.20', true, true);
-- "bad" carrier - "eastco" at 172.39.0.21 (returns 503)
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('1d8ef351-062a-4487-94f8-7698d5a40d24', 'eastco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('e71519ff-4494-4c98-a06a-324e2712d94b', '1d8ef351-062a-4487-94f8-7698d5a40d24', '172.39.0.21', true, true);
-- "bad" carrier - "northco" at 172.39.0.22 (returns 100 Trying and never answers)
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('7b4b9c56-4d81-4f31-9d70-62cd7d82193b', 'northco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('fcbb2109-582c-4d55-b38a-b6bc7cc4be73', '7b4b9c56-4d81-4f31-9d70-62cd7d82193b', '172.39.0.22', true, true);
-- lcr route: first try eastco (503) then northco (cancel), then westco (200)
insert into lcr(lcr_sid, name, is_active, service_provider_sid) values ('0eba4204-b036-4388-8f47-724c4cfb3d4f', 'default LCR', 1, '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into lcr_routes(lcr_route_sid, lcr_sid, regex, priority) values ('0eba4204-b036-4388-8f47-724c4cfb3d4e', '0eba4204-b036-4388-8f47-724c4cfb3d4f', '.*', 1);
insert into lcr_carrier_set_entry(lcr_carrier_set_entry_sid, lcr_route_sid, voip_carrier_sid, priority)
values ('b677a7b5-bec6-4045-ae4a-a67a5ccb3448', '0eba4204-b036-4388-8f47-724c4cfb3d4e', '1d8ef351-062a-4487-94f8-7698d5a40d24', 3);
insert into lcr_carrier_set_entry(lcr_carrier_set_entry_sid, lcr_route_sid, voip_carrier_sid, priority)
values ('b417ce66-a805-46b3-b296-697a6c2ca249', '0eba4204-b036-4388-8f47-724c4cfb3d4e', '7b4b9c56-4d81-4f31-9d70-62cd7d82193b', 1);
insert into lcr_carrier_set_entry(lcr_carrier_set_entry_sid, lcr_route_sid, voip_carrier_sid, priority)
values ('13e344a0-8a4c-4f95-8a19-ccbfc7ab053e', '0eba4204-b036-4388-8f47-724c4cfb3d4e', '287c1452-620d-4195-9f19-c9814ef90d78', 2);
+2 -2
View File
@@ -33,7 +33,7 @@
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-carrier-success
Subject: uac-cancel
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: phone
@@ -60,7 +60,7 @@
<recv response="100" response_txn="invite"/>
<pause milliseconds="3000"/>
<pause milliseconds="1000"/>
<send>
<![CDATA[
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC with media">
<send retrans="500">
<![CDATA[
INVITE sip:16173333456@127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:16173333456@127.0.0.1>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: phone
X-Voip-Carrier-Sid: 287c1452-620d-4195-9f19-c9814ef90d78
Subject: uac-pcap-carrier-success
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[local_ip_type] [local_ip]
t=0 0
m=audio [auto_media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-11,16
]]>
</send>
<recv response="100" optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="200" rtd="true" crlf="true">
</recv>
<send>
<![CDATA[
ACK sip:sip:+16173333456@127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-pcap-carrier-success
Content-Length: 0
]]>
</send>
<!-- Play a pre-recorded PCAP file (RTP stream) -->
<nop>
<action>
<exec play_pcap_audio="pcap/g711a.pcap"/>
</action>
</nop>
<!-- Pause briefly -->
<pause milliseconds="2000"/>
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
<send retrans="500">
<![CDATA[
BYE sip:sip:+16173333456@127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 BYE
Max-Forwards: 70
Subject: uac-pcap-carrier-success
Content-Length: 0
]]>
</send>
<recv response="200" crlf="true">
</recv>
</scenario>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC with media">
<send retrans="500">
<![CDATA[
INVITE sip:16173333456@172.39.0.20 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:16173333456@172.39.0.20>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: sip
Subject: uac-pcap-carrier-success
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[local_ip_type] [local_ip]
t=0 0
m=audio [auto_media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-11,16
]]>
</send>
<recv response="100" optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="200" rtd="true" crlf="true">
</recv>
<send>
<![CDATA[
ACK sip:sip:+16173333456@127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-pcap-carrier-success
Content-Length: 0
]]>
</send>
<!-- Play a pre-recorded PCAP file (RTP stream) -->
<nop>
<action>
<exec play_pcap_audio="pcap/g711a.pcap"/>
</action>
</nop>
<!-- Pause briefly -->
<pause milliseconds="2000"/>
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
<send retrans="500">
<![CDATA[
BYE sip:sip:+16173333456@127.0.0.1 SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 BYE
Max-Forwards: 70
Subject: uac-pcap-carrier-success
Content-Length: 0
]]>
</send>
<recv response="200" crlf="true">
</recv>
</scenario>
+11 -3
View File
@@ -44,6 +44,14 @@ test('sbc-outbound tests', async(t) => {
await sippUac('uac-pcap-carrier-success.xml');
t.pass('successfully completed outbound call to sip trunk');
/* call to Sip URI with no lcr configured */
await sippUac('uac-pcap-sip-routing-success.xml');
t.pass('successfully completed outbound call to sip routing trunk');
/* call to PSTN with no lcr configured */
await sippUac('uac-pcap-inbound-carrier-success.xml');
t.pass('successfully completed outbound call to sip trunk');
/* call to PSTN with request uri we see in kubernetes */
await sippUac('uac-pcap-carrier-success-k8s.xml');
t.pass('successfully completed outbound call to sip trunk (k8S req uri)');
@@ -54,7 +62,7 @@ test('sbc-outbound tests', async(t) => {
/* call to PSTN with lcr configured */
await sippUac('uac-pcap-carrier-success.xml');
t.pass('successfully completed outbound lcr carrier with crankback after failure');
t.pass('successfully completed outbound call using LCR');
// re-rack test data
execSync(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/jambones-sql.sql`);
@@ -87,8 +95,8 @@ test('sbc-outbound tests', async(t) => {
await waitFor(25);
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
console.log(`cdrs: ${JSON.stringify(res)}`);
t.ok(res.total === 6, 'wrote 6 cdrs');
console.log(`${res.total} cdrs: ${JSON.stringify(res)}`);
t.ok(res.total === 8, 'wrote 8 cdrs');
srf.disconnect();
} catch (err) {