mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 18:44:38 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9c4e01c36 | ||
|
|
3d902c65a4 | ||
|
|
d641504797 | ||
|
|
b871812a70 | ||
|
|
e123a2ef88 | ||
|
|
775e63518a | ||
|
|
307cf9bd65 | ||
|
|
f48ca4821e | ||
|
|
43ed2bafe1 | ||
|
|
5ee041bdee | ||
|
|
5bbe6a8752 | ||
|
|
2e5d609bab | ||
|
|
8f598bf7b0 | ||
|
|
c7d717b3ee | ||
|
|
e9209b37ca | ||
|
|
3b98dc6ec2 | ||
|
|
1c84dd799c | ||
|
|
4338ae9411 | ||
|
|
e17e7dbddd | ||
|
|
254479e289 | ||
|
|
a10a311dcb | ||
|
|
806cb89c37 | ||
|
|
fffa2748d1 | ||
|
|
76625c7596 | ||
|
|
3b0f7ff6eb | ||
|
|
33c75acc9e |
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.6.0-alpine as base
|
||||
FROM --platform=linux/amd64 node:18.12.1-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ const opts = Object.assign({
|
||||
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
||||
const logger = require('pino')(opts);
|
||||
const {
|
||||
writeCallCount,
|
||||
writeCallCountSP,
|
||||
writeCallCountApp,
|
||||
writeCdrs,
|
||||
queryCdrs,
|
||||
writeAlerts,
|
||||
@@ -39,7 +42,8 @@ const {
|
||||
lookupAccountBySid,
|
||||
lookupAccountCapacitiesBySid,
|
||||
lookupSipGatewaysByCarrier,
|
||||
lookupCarrierBySid
|
||||
lookupCarrierBySid,
|
||||
queryCallLimits
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
@@ -64,6 +68,9 @@ const activeCallIds = new Map();
|
||||
|
||||
srf.locals = {...srf.locals,
|
||||
stats,
|
||||
writeCallCount,
|
||||
writeCallCountSP,
|
||||
writeCallCountApp,
|
||||
writeCdrs,
|
||||
writeAlerts,
|
||||
AlertType,
|
||||
@@ -77,7 +84,8 @@ srf.locals = {...srf.locals,
|
||||
lookupAccountBySid,
|
||||
lookupAccountCapacitiesBySid,
|
||||
lookupSipGatewaysByCarrier,
|
||||
lookupCarrierBySid
|
||||
lookupCarrierBySid,
|
||||
queryCallLimits
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
createHash,
|
||||
@@ -91,10 +99,12 @@ const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
});
|
||||
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, {
|
||||
emitter: stats,
|
||||
//emitter: stats,
|
||||
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22225,
|
||||
protocol: 'udp'
|
||||
protocol: ngProtocol
|
||||
});
|
||||
srf.locals.getRtpEngine = getRtpEngine;
|
||||
|
||||
@@ -174,7 +184,6 @@ if ('test' !== process.env.NODE_ENV) {
|
||||
}
|
||||
|
||||
const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
||||
logger.debug(`dns lookup for ${serviceName}..`);
|
||||
lookup(serviceName, {family: 4, all: true}, (err, addresses) => {
|
||||
if (err) {
|
||||
logger.error({err}, `Error looking up ${serviceName}`);
|
||||
@@ -186,7 +195,7 @@ const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
||||
rtpServers.length = 0;
|
||||
Array.prototype.push.apply(rtpServers, addrs);
|
||||
logger.info({rtpServers}, 'rtpserver endpoints have been updated');
|
||||
setRtpEngines(rtpServers.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
|
||||
setRtpEngines(rtpServers.map((a) => `${a}:${ngPort}`));
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -213,7 +222,7 @@ else {
|
||||
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
|
||||
if (!equalsIgnoreOrder(newArray, rtpServers)) {
|
||||
logger.info({newArray}, 'resetting active rtpengines');
|
||||
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
|
||||
setRtpEngines(newArray.map((a) => `${a}:${ngPort}`));
|
||||
rtpServers.length = 0;
|
||||
Array.prototype.push.apply(rtpServers, newArray);
|
||||
}
|
||||
|
||||
+52
-23
@@ -1,7 +1,7 @@
|
||||
const Emitter = require('events');
|
||||
const sdpTransform = require('sdp-transform');
|
||||
const SrsClient = require('@jambonz/siprec-client-utils');
|
||||
const {makeRtpEngineOpts, makeCallCountKey} = require('./utils');
|
||||
const {makeRtpEngineOpts, nudgeCallCounts} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {SipError, stringifyUri, parseUri} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-outbound');
|
||||
@@ -13,10 +13,17 @@ const makeInviteInProgressKey = (callid) => `sbc-out-iip${callid}`;
|
||||
*/
|
||||
const createBLegFromHeader = (req, teams) => {
|
||||
const from = req.getParsedHeader('From');
|
||||
const host = teams ? req.get('X-MS-Teams-Tenant-FQDN') : 'localhost';
|
||||
const uri = parseUri(from.uri);
|
||||
if (uri && uri.user) return `sip:${uri.user}@${host}`;
|
||||
return `sip:anonymous@${host}`;
|
||||
let user = uri.user || 'anonymous';
|
||||
let host = 'localhost';
|
||||
if (teams) {
|
||||
host = req.get('X-MS-Teams-Tenant-FQDN');
|
||||
}
|
||||
else if (req.has('X-Preferred-From-User') || req.has('X-Preferred-From-Host')) {
|
||||
user = req.get('X-Preferred-From-User') || user;
|
||||
host = req.get('X-Preferred-From-Host') || host;
|
||||
}
|
||||
return `sip:${user}@${host}`;
|
||||
};
|
||||
const createBLegToHeader = (req, teams) => {
|
||||
const to = req.getParsedHeader('To');
|
||||
@@ -33,11 +40,13 @@ const initCdr = (srf, req) => {
|
||||
const to = arr ? arr[1] : req.calledNumber;
|
||||
arr = regex.exec(req.callingNumber);
|
||||
const from = arr ? arr[1] : req.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'),
|
||||
...(applicationSid && {application_sid: applicationSid}),
|
||||
from,
|
||||
to,
|
||||
duration: 0,
|
||||
@@ -71,9 +80,7 @@ class CallSession extends Emitter {
|
||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||
this.writeCdrs = this.srf.locals.writeCdrs;
|
||||
|
||||
this.incrKey = req.srf.locals.realtimeDbHelpers.incrKey;
|
||||
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
||||
this.callCountKey = makeCallCountKey(req.locals.account_sid);
|
||||
|
||||
const {performLcr, lookupCarrierBySid, lookupSipGatewaysByCarrier} = this.srf.locals.dbHelpers;
|
||||
this.performLcr = performLcr;
|
||||
@@ -87,6 +94,10 @@ class CallSession extends Emitter {
|
||||
return this.req.locals.account_sid;
|
||||
}
|
||||
|
||||
get application_sid() {
|
||||
return this.req.locals.application_sid;
|
||||
}
|
||||
|
||||
get privateSipAddress() {
|
||||
return this.srf.locals.privateSipAddress;
|
||||
}
|
||||
@@ -313,13 +324,14 @@ class CallSession extends Emitter {
|
||||
'-X-MS-Teams-FQDN',
|
||||
'-X-MS-Teams-Tenant-FQDN',
|
||||
'-X-Trace-ID',
|
||||
'X-CID',
|
||||
'-Allow',
|
||||
'-Session-Expires',
|
||||
'-X-Requested-Carrier-Sid',
|
||||
'-X-Jambonz-Routing',
|
||||
'-X-Jambonz-FS-UUID',
|
||||
'Min-SE'
|
||||
'-X-Preferred-From-User',
|
||||
'X-Preferred-From-Host',
|
||||
'-X-Jambonz-FS-UUID',
|
||||
],
|
||||
proxyResponseHeaders: [
|
||||
'all',
|
||||
@@ -361,7 +373,9 @@ class CallSession extends Emitter {
|
||||
if (!this.req.locals.account.disable_cdrs) {
|
||||
this.req.locals.cdr = {
|
||||
...initCdr(this.req.srf, 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}),
|
||||
trunk
|
||||
};
|
||||
}
|
||||
@@ -417,6 +431,7 @@ class CallSession extends Emitter {
|
||||
sip_status: status
|
||||
}).catch((err) => this.logger.error({err}, 'Error writing cdr for call failure'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this.logger.info(`got ${err.status}, cranking back to next destination`);
|
||||
@@ -455,13 +470,19 @@ class CallSession extends Emitter {
|
||||
await other.destroy();
|
||||
} catch (err) {}
|
||||
|
||||
this.decrKey(this.callCountKey)
|
||||
.then((count) => {
|
||||
this.logger.debug(`after hangup there are ${count} active calls for this account`);
|
||||
debug(`after hangup there are ${count} active calls for this account`);
|
||||
return;
|
||||
})
|
||||
.catch((err) => this.logger.error({err}, 'Error decrementing call count'));
|
||||
const trackingOn = process.env.JAMBONES_TRACK_ACCOUNT_CALLS ||
|
||||
process.env.JAMBONES_TRACK_SP_CALLS ||
|
||||
process.env.JAMBONES_TRACK_APP_CALLS;
|
||||
|
||||
if (process.env.JAMBONES_HOSTING || trackingOn) {
|
||||
const {writeCallCount, writeCallCountSP, writeCallCountApp} = this.req.srf.locals;
|
||||
await nudgeCallCounts(this.logger, {
|
||||
service_provider_sid: this.service_provider_sid,
|
||||
account_sid: this.account_sid,
|
||||
application_sid: this.application_sid
|
||||
}, this.decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||
.catch((err) => this.logger.error(err, 'Error decrementing call counts'));
|
||||
}
|
||||
|
||||
/* write cdr for connected call */
|
||||
if (this.req.locals.cdr) {
|
||||
@@ -550,11 +571,17 @@ Duration=${payload.duration} `
|
||||
async _onReinvite(dlg, req, res) {
|
||||
try {
|
||||
const reason = req.get('X-Reason');
|
||||
const isReleasingMedia = reason && dlg.type === 'uas' && ['release-media', 'anchor-media'].includes(reason);
|
||||
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
|
||||
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
|
||||
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
|
||||
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
|
||||
const direction = dlg.type === 'uas' ? ['private', 'public'] : ['public', 'private'];
|
||||
if (isReleasingMedia) {
|
||||
if (!offerMedia.flags.includes('port latching')) offerMedia.flags.push('port latching');
|
||||
if (!offerMedia.flags.includes('asymmetric')) offerMedia.flags.push('asymmetric');
|
||||
offerMedia.flags = offerMedia.flags.filter((f) => f !== 'media handover');
|
||||
}
|
||||
let opts = {
|
||||
...this.rtpEngineOpts.common,
|
||||
...offerMedia,
|
||||
@@ -565,19 +592,21 @@ Duration=${payload.duration} `
|
||||
};
|
||||
if (reason && opts.flags && !opts.flags.includes('reset')) opts.flags.push('reset');
|
||||
|
||||
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.debug({opts, response}, 'CallSession:_onReinvite: (offer)');
|
||||
|
||||
/* if this is a re-invite from the FS to change media anchoring, avoid sending the reinvite out */
|
||||
let sdp;
|
||||
if (reason && dlg.type === 'uas' && ['release-media', 'anchor-media'].includes(reason)) {
|
||||
if (isReleasingMedia) {
|
||||
this.logger.info(`got a reinvite from FS to ${reason}`);
|
||||
sdp = dlg.other.remote.sdp;
|
||||
answerMedia.flags = ['asymmetric', 'port latching'];
|
||||
if (!answerMedia.flags.includes('port latching')) answerMedia.flags.push('port latching');
|
||||
if (!answerMedia.flags.includes('asymmetric')) answerMedia.flags.push('asymmetric');
|
||||
answerMedia.flags = answerMedia.flags.filter((f) => f !== 'media handover');
|
||||
this._mediaReleased = 'release-media' === reason;
|
||||
}
|
||||
else {
|
||||
@@ -596,7 +625,7 @@ Duration=${payload.duration} `
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({sdp: response.sdp}, 'CallSession:_onReinvite: sending back upstream');
|
||||
this.logger.debug({opts, sdp: response.sdp}, 'CallSession:_onReinvite: (answer) sending back upstream');
|
||||
res.send(200, {body: response.sdp});
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
@@ -785,10 +814,10 @@ Duration=${payload.duration} `
|
||||
res.send(202);
|
||||
|
||||
// invite to new fs
|
||||
const headers = {};
|
||||
if (req.has('X-Retain-Call-Sid')) {
|
||||
Object.assign(headers, {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')});
|
||||
}
|
||||
const headers = {
|
||||
...(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;
|
||||
|
||||
+76
-35
@@ -1,7 +1,7 @@
|
||||
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 {selectHostPort, nudgeCallCounts} = require('./utils');
|
||||
const FS_UUID_SET_NAME = 'fsUUIDs';
|
||||
|
||||
module.exports = (srf, logger, opts) => {
|
||||
@@ -10,13 +10,15 @@ module.exports = (srf, logger, opts) => {
|
||||
const registrar = new Registrar(opts);
|
||||
const {
|
||||
lookupAccountCapacitiesBySid,
|
||||
lookupAccountBySid
|
||||
lookupAccountBySid,
|
||||
queryCallLimits
|
||||
} = srf.locals.dbHelpers;
|
||||
|
||||
const initLocals = async(req, res, next) => {
|
||||
req.locals = req.locals || {};
|
||||
const callId = req.get('Call-ID');
|
||||
req.locals.account_sid = req.get('X-Account-Sid');
|
||||
req.locals.application_sid = req.get('X-Application-Sid');
|
||||
const traceId = req.locals.trace_id = req.get('X-Trace-ID');
|
||||
req.locals.logger = logger.child({
|
||||
callId,
|
||||
@@ -63,7 +65,6 @@ module.exports = (srf, logger, opts) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stats.increment('sbc.invites', ['direction:outbound']);
|
||||
|
||||
req.on('cancel', () => {
|
||||
@@ -76,6 +77,7 @@ module.exports = (srf, logger, opts) => {
|
||||
|
||||
try {
|
||||
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
|
||||
req.locals.service_provider_sid = req.locals.account.service_provider_sid;
|
||||
} catch (err) {
|
||||
req.locals.logger.error({err}, `Error looking up account sid ${req.locals.account_sid}`);
|
||||
res.send(500);
|
||||
@@ -85,26 +87,27 @@ module.exports = (srf, logger, opts) => {
|
||||
};
|
||||
|
||||
const checkLimits = async(req, res, next) => {
|
||||
const {logger, account_sid} = req.locals;
|
||||
const {writeAlerts, AlertType} = req.srf.locals;
|
||||
const {logger, account_sid, service_provider_sid, application_sid} = req.locals;
|
||||
const trackingOn = process.env.JAMBONES_TRACK_ACCOUNT_CALLS ||
|
||||
process.env.JAMBONES_TRACK_SP_CALLS ||
|
||||
process.env.JAMBONES_TRACK_APP_CALLS;
|
||||
if (!process.env.JAMBONES_HOSTING && !trackingOn) {
|
||||
logger.debug('tracking is off, skipping call limit checks');
|
||||
return next(); // skip
|
||||
}
|
||||
|
||||
const {writeCallCount, writeCallCountSP, writeCallCountApp, writeAlerts, AlertType} = req.srf.locals;
|
||||
|
||||
const key = makeCallCountKey(account_sid);
|
||||
try {
|
||||
/* increment the call count */
|
||||
const calls = await incrKey(key);
|
||||
debug(`checkLimits: call count is now ${calls}`);
|
||||
|
||||
/* decrement count if INVITE is later rejected */
|
||||
res.once('end', ({status}) => {
|
||||
res.once('end', async({status}) => {
|
||||
if (status > 200) {
|
||||
debug('checkLimits: decrementing call count due to rejection');
|
||||
decrKey(key)
|
||||
.then((count) => {
|
||||
logger.debug({key}, `after rejection there are ${count} active calls for this account`);
|
||||
debug({key}, `after rejection there are ${count} active calls for this account`);
|
||||
return;
|
||||
})
|
||||
.catch((err) => logger.error({err}, 'checkLimits: decrKey err'));
|
||||
nudgeCallCounts(logger, {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
application_sid
|
||||
}, decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||
.catch((err) => logger.error(err, 'Error decrementing call counts'));
|
||||
const tags = ['accepted:no', `sipStatus:${status}`];
|
||||
stats.increment('sbc.originations', tags);
|
||||
}
|
||||
@@ -114,6 +117,13 @@ module.exports = (srf, logger, opts) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* increment the call count */
|
||||
const {callsSP, calls} = await nudgeCallCounts(logger, {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
application_sid
|
||||
}, incrKey, {writeCallCountSP, writeCallCount, writeCallCountApp});
|
||||
|
||||
/* compare to account's limit, though avoid db hit when call count is low */
|
||||
const minLimit = process.env.MIN_CALL_LIMIT ?
|
||||
parseInt(process.env.MIN_CALL_LIMIT) :
|
||||
@@ -122,23 +132,54 @@ module.exports = (srf, logger, opts) => {
|
||||
|
||||
const capacities = await lookupAccountCapacitiesBySid(account_sid);
|
||||
const limit = capacities.find((c) => c.category == 'voice_call_session');
|
||||
if (!limit) {
|
||||
logger.debug('checkLimits: no call limits specified');
|
||||
return next();
|
||||
if (limit) {
|
||||
const limit_sessions = limit.quantity;
|
||||
|
||||
if (calls > limit_sessions) {
|
||||
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.ACCOUNT_CALL_LIMIT,
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
count: limit_sessions
|
||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||
res.send(503, 'Maximum Calls In Progress');
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
}
|
||||
const limit_sessions = limit.quantity;
|
||||
|
||||
if (calls > limit_sessions) {
|
||||
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
|
||||
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.CALL_LIMIT,
|
||||
account_sid,
|
||||
count: limit_sessions
|
||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||
|
||||
res.send(503, 'Maximum Calls In Progress');
|
||||
return req.srf.endSession(req);
|
||||
else if (trackingOn) {
|
||||
const {account_limit, sp_limit} = await queryCallLimits(service_provider_sid, account_sid);
|
||||
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS && account_limit > 0 && calls > account_limit) {
|
||||
logger.info({calls, account_limit}, 'checkLimits: account limits exceeded');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.ACCOUNT_CALL_LIMIT,
|
||||
service_provider_sid: service_provider_sid,
|
||||
account_sid,
|
||||
count: calls
|
||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||
res.send(503, 'Max Account Calls In Progress', {
|
||||
headers: {
|
||||
'X-Account-Sid': account_sid,
|
||||
'X-Call-Limit': account_limit
|
||||
}
|
||||
});
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
if (process.env.JAMBONES_TRACK_SP_CALLS && sp_limit > 0 && callsSP > sp_limit) {
|
||||
logger.info({callsSP, sp_limit}, 'checkLimits: service provider limits exceeded');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.SP_CALL_LIMIT,
|
||||
service_provider_sid: service_provider_sid,
|
||||
count: callsSP
|
||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||
res.send(503, 'Max Service Provider Calls In Progress', {
|
||||
headers: {
|
||||
'X-Service-Provider-Sid': service_provider_sid,
|
||||
'X-Call-Limit': sp_limit
|
||||
}
|
||||
});
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
}
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
+74
-7
@@ -4,9 +4,11 @@ const debug = require('debug')('jambonz:sbc-outbound');
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const from = req.getParsedHeader('from');
|
||||
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
|
||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCharacteristics;
|
||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCharacteristics;
|
||||
const rtpCopy = JSON.parse(JSON.stringify(rtpCharacteristics));
|
||||
const srtpCopy = JSON.parse(JSON.stringify(srtpCharacteristics));
|
||||
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
|
||||
/* webrtc clients (e.g. sipjs) send DMTF via SIP INFO */
|
||||
if ((srcIsUsingSrtp || dstIsUsingSrtp) && !teams) {
|
||||
@@ -15,7 +17,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
}
|
||||
const common = {
|
||||
'call-id': req.get('Call-ID'),
|
||||
'replace': ['origin', 'session-connection']
|
||||
'replace': ['origin', 'session-connection'],
|
||||
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
||||
};
|
||||
return {
|
||||
common,
|
||||
@@ -77,7 +80,9 @@ const pingMsTeamsGateways = (logger, srf) => {
|
||||
});
|
||||
};
|
||||
|
||||
const makeCallCountKey = (sid) => `${sid}:outcalls`;
|
||||
const makeAccountCallCountKey = (sid) => `outcalls:account:${sid}`;
|
||||
const makeSPCallCountKey = (sid) => `outcalls:sp:${sid}`;
|
||||
const makeAppCallCountKey = (sid) => `outcalls:app:${sid}`;
|
||||
|
||||
const equalsIgnoreOrder = (a, b) => {
|
||||
if (a.length !== b.length) return false;
|
||||
@@ -110,12 +115,74 @@ const createHealthCheckApp = (port, logger) => {
|
||||
});
|
||||
};
|
||||
|
||||
const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
|
||||
const {service_provider_sid, account_sid, application_sid} = sids;
|
||||
const {writeCallCount, writeCallCountSP, writeCallCountApp} = writers;
|
||||
const nudges = [];
|
||||
const writes = [];
|
||||
|
||||
logger.debug(sids, 'nudgeCallCounts');
|
||||
|
||||
if (process.env.JAMBONES_TRACK_SP_CALLS) {
|
||||
const key = makeSPCallCountKey(service_provider_sid);
|
||||
nudges.push(nudgeOperator(key));
|
||||
}
|
||||
else {
|
||||
nudges.push(() => Promise.resolve(null));
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) {
|
||||
const key = makeAccountCallCountKey(account_sid);
|
||||
nudges.push(nudgeOperator(key));
|
||||
}
|
||||
else {
|
||||
nudges.push(() => Promise.resolve(null));
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) {
|
||||
const key = makeAppCallCountKey(application_sid);
|
||||
nudges.push(nudgeOperator(key));
|
||||
}
|
||||
else {
|
||||
nudges.push(() => Promise.resolve(null));
|
||||
}
|
||||
|
||||
try {
|
||||
const [callsSP, calls, callsApp] = await Promise.all(nudges);
|
||||
logger.debug({
|
||||
calls, callsSP, callsApp,
|
||||
service_provider_sid, account_sid, application_sid}, 'call counts after adjustment');
|
||||
if (process.env.JAMBONES_TRACK_SP_CALLS) {
|
||||
writes.push(writeCallCountSP({service_provider_sid, calls_in_progress: callsSP}));
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) {
|
||||
writes.push(writeCallCount({service_provider_sid, account_sid, calls_in_progress: calls}));
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) {
|
||||
writes.push(writeCallCountApp({service_provider_sid, account_sid, application_sid, calls_in_progress: callsApp}));
|
||||
}
|
||||
|
||||
/* write the call counts to the database */
|
||||
Promise.all(writes).catch((err) => logger.error({err}, 'Error writing call counts'));
|
||||
|
||||
return {callsSP, calls, callsApp};
|
||||
} catch (err) {
|
||||
logger.error(err, 'error incrementing call counts');
|
||||
}
|
||||
|
||||
return {callsSP: null, calls: null, callsApp: null};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
makeRtpEngineOpts,
|
||||
selectHostPort,
|
||||
pingMsTeamsGateways,
|
||||
makeCallCountKey,
|
||||
makeAccountCallCountKey,
|
||||
makeSPCallCountKey,
|
||||
equalsIgnoreOrder,
|
||||
systemHealth,
|
||||
createHealthCheckApp
|
||||
createHealthCheckApp,
|
||||
nudgeCallCounts
|
||||
};
|
||||
|
||||
Generated
+1095
-347
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-outbound",
|
||||
"version": "v0.7.5",
|
||||
"version": "v0.7.7",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -22,19 +22,19 @@
|
||||
"description": "jambonz session border controller application for outbound calls",
|
||||
"scripts": {
|
||||
"start": "node app",
|
||||
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONZ_HOSTING=1 JAMBONES_NETWORK_CIDR=127.0.0.1/32 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_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 node test/ ",
|
||||
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_HOSTING=1 JAMBONES_NETWORK_CIDR=127.0.0.1/32 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_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 node test/ ",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.6.18",
|
||||
"@jambonz/db-helpers": "^0.7.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.35",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.2",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||
"@jambonz/rtpengine-utils": "^0.3.1",
|
||||
"@jambonz/rtpengine-utils": "^0.3.10",
|
||||
"@jambonz/siprec-client-utils": "^0.1.4",
|
||||
"@jambonz/stats-collector": "^0.1.6",
|
||||
"@jambonz/time-series": "^0.1.12",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"drachtio-fn-b2b-sugar": "^0.0.12",
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ test('sbc-outbound tests', async(t) => {
|
||||
|
||||
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');
|
||||
t.ok(res.total === 5, 'wrote 5 cdrs');
|
||||
|
||||
srf.disconnect();
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user