mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 02:04:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e6dc35e86 | ||
|
|
16a4709b7d | ||
|
|
01adb5cbf0 | ||
|
|
49ba872f51 |
@@ -97,6 +97,7 @@ srf.locals = {...srf.locals,
|
||||
lookupCarrierByAccountLcr
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
client: redisClient,
|
||||
createHash,
|
||||
retrieveHash,
|
||||
incrKey,
|
||||
|
||||
+31
-12
@@ -1,7 +1,7 @@
|
||||
const Emitter = require('events');
|
||||
const sdpTransform = require('sdp-transform');
|
||||
const SrsClient = require('@jambonz/siprec-client-utils');
|
||||
const {makeRtpEngineOpts, nudgeCallCounts, isPrivateVoipNetwork} = require('./utils');
|
||||
const {makeRtpEngineOpts, nudgeCallCounts, isPrivateVoipNetwork, isBlackListedSipGateway} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {SipError, stringifyUri, parseUri} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-outbound');
|
||||
@@ -168,7 +168,7 @@ class CallSession extends Emitter {
|
||||
subscribeAnswer,
|
||||
unsubscribe
|
||||
} = engine;
|
||||
const {createHash, retrieveHash} = this.srf.locals.realtimeDbHelpers;
|
||||
const {client, createHash, retrieveHash} = this.srf.locals.realtimeDbHelpers;
|
||||
this.offer = offer;
|
||||
this.answer = answer;
|
||||
this.del = del;
|
||||
@@ -186,7 +186,7 @@ class CallSession extends Emitter {
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, this.useWss || teams, false, teams);
|
||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||
let proxy, uris;
|
||||
let proxy, uris = [];
|
||||
const mapGateways = new Map();
|
||||
|
||||
try {
|
||||
@@ -280,7 +280,13 @@ class CallSession extends Emitter {
|
||||
}
|
||||
const vc = await this.lookupCarrierBySid(voip_carrier_sid);
|
||||
const gateways = await this.lookupSipGatewaysByCarrier(voip_carrier_sid);
|
||||
const gws = (gateways || [])
|
||||
const goodGateways = [];
|
||||
for (const g of gateways) {
|
||||
if (!await isBlackListedSipGateway(client, this.logger, g.sip_gateway_sid)) {
|
||||
goodGateways.push(g);
|
||||
}
|
||||
}
|
||||
const gws = (goodGateways || [])
|
||||
.filter((gw) => gw.outbound);
|
||||
if (gws.length) {
|
||||
uris = [];
|
||||
@@ -332,6 +338,7 @@ class CallSession extends Emitter {
|
||||
}
|
||||
else {
|
||||
this.logger.info({voip_carrier_sid}, 'no outbound gateways found for requested carrier');
|
||||
this.res.send(603);
|
||||
}
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
@@ -775,6 +782,8 @@ Duration=${payload.duration} `
|
||||
sdp
|
||||
};
|
||||
response = await this.answer(opts);
|
||||
/* now remove asymeetric as B party (looking at you Genesys ring group) may need port re-learning on invites */
|
||||
answerMedia.flags = answerMedia.flags.filter((f) => f !== 'asymmetric');
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
@@ -860,8 +869,6 @@ Duration=${payload.duration} `
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.start()));
|
||||
// Only take who success accept request.
|
||||
this.srsClients = this.srsClients.filter((c) => c.activated);
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error starting SipRec call recording');
|
||||
@@ -869,14 +876,18 @@ Duration=${payload.duration} `
|
||||
}
|
||||
}
|
||||
else if (reason === 'stopCallRecording') {
|
||||
if (!this.srsClients.length) {
|
||||
if (!this.srsClients.length || !this.srsClients.some((c) => c.activated)) {
|
||||
res.send(400);
|
||||
this.logger.info('discarding stopCallRecording request because we are not recording');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.stop()));
|
||||
await Promise.any(this.srsClients.map((c) => {
|
||||
if (c.activated) {
|
||||
c.stop();
|
||||
}
|
||||
}));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error stopping SipRec call recording');
|
||||
@@ -885,14 +896,18 @@ Duration=${payload.duration} `
|
||||
this.srsClients = [];
|
||||
}
|
||||
else if (reason === 'pauseCallRecording') {
|
||||
if (!this.srsClients.length || this.srsClients.every((c) => c.paused)) {
|
||||
if (!this.srsClients.length || !this.srsClients.some((c) => c.activated && !c.paused)) {
|
||||
this.logger.info('discarding invalid pauseCallRecording request');
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.pause({headers})));
|
||||
await Promise.any(this.srsClients.map((c) => {
|
||||
if (c.activated && !c.paused) {
|
||||
c.pause({headers});
|
||||
}
|
||||
}));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error pausing SipRec call recording');
|
||||
@@ -900,14 +915,18 @@ Duration=${payload.duration} `
|
||||
}
|
||||
}
|
||||
else if (reason === 'resumeCallRecording') {
|
||||
if (!this.srsClients.length || !this.srsClients.every((c) => c.paused)) {
|
||||
if (!this.srsClients.length || !this.srsClients.some((c) => c.activated && c.paused)) {
|
||||
res.send(400);
|
||||
this.logger.info('discarding invalid resumeCallRecording request');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._startRecordingNoAnswerTimer(res);
|
||||
await Promise.any(this.srsClients.map((c) => c.resume({headers})));
|
||||
await Promise.any(this.srsClients.map((c) => {
|
||||
if (c.activated && c.paused) {
|
||||
c.resume({headers});
|
||||
}
|
||||
}));
|
||||
succeeded = true;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error resuming SipRec call recording');
|
||||
|
||||
+14
-1
@@ -213,6 +213,18 @@ const isPrivateVoipNetwork = async(uri) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
function makeBlacklistGatewayKey(key) {
|
||||
return `blacklist-sip-gateway:${key}`;
|
||||
}
|
||||
|
||||
async function isBlackListedSipGateway(client, logger, sip_gateway_sid) {
|
||||
try {
|
||||
return await client.exists(makeBlacklistGatewayKey(sip_gateway_sid)) === 1;
|
||||
} catch (err) {
|
||||
logger.error({err}, `isBlackListedSipGateway: error while checking blacklist for ${sip_gateway_sid}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeRtpEngineOpts,
|
||||
selectHostPort,
|
||||
@@ -223,5 +235,6 @@ module.exports = {
|
||||
systemHealth,
|
||||
createHealthCheckApp,
|
||||
nudgeCallCounts,
|
||||
isPrivateVoipNetwork
|
||||
isPrivateVoipNetwork,
|
||||
isBlackListedSipGateway
|
||||
};
|
||||
|
||||
Generated
+1441
-781
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-outbound",
|
||||
"version": "0.8.5",
|
||||
"version": "0.9.0",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -28,26 +28,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.4",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/mw-registrar": "0.2.7",
|
||||
"@jambonz/rtpengine-utils": "^0.4.4",
|
||||
"@jambonz/siprec-client-utils": "^0.2.7",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"drachtio-fn-b2b-sugar": "^0.0.12",
|
||||
"drachtio-fn-b2b-sugar": "^0.1.0",
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.18.1",
|
||||
"pino": "^7.11.0",
|
||||
"sdp-transform": "^2.14.1"
|
||||
"express": "^4.19.2",
|
||||
"pino": "^8.20.0",
|
||||
"sdp-transform": "^2.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bent": "^7.3.12",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"nyc": "^15.1.0",
|
||||
"tape": "^5.5.3"
|
||||
"tape": "^5.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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: ff49e33-e771-403a-8c99-1780eabbc803
|
||||
X-Jambonz-Routing: phone
|
||||
Subject: uac-pcap-carrier-fail-limits
|
||||
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="603" rtd="true" crlf="true">
|
||||
</recv>
|
||||
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:sip:+16173333456@127.0.0.1 SIP/2.0
|
||||
[last_Via]
|
||||
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-fail-blacklist
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
|
||||
</scenario>
|
||||
+12
-1
@@ -26,6 +26,7 @@ function connect(connectable) {
|
||||
test('sbc-outbound tests', async(t) => {
|
||||
const {srf} = require('../app');
|
||||
const { queryCdrs } = srf.locals;
|
||||
const redisClient = srf.locals.realtimeDbHelpers.client;
|
||||
|
||||
try {
|
||||
await connect(srf);
|
||||
@@ -95,7 +96,17 @@ test('sbc-outbound tests', async(t) => {
|
||||
/* fails when session limit exceeded */
|
||||
await sippUac('uac-pcap-carrier-fail-limits.xml');
|
||||
t.pass('fails when max calls in progress');
|
||||
|
||||
|
||||
// re-rack test data
|
||||
execSync(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/jambones-sql.sql`);
|
||||
execSync(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/populate-test-data.sql`);
|
||||
|
||||
// Black list good carrier for some seconds
|
||||
await redisClient.setex('blacklist-sip-gateway:124a5339-c62c-4075-9e19-f4de70a96597', 3, '');
|
||||
await sippUac('uac-pcap-carrier-fail-blacklist.xml');
|
||||
t.pass('fails when carrier is blacklisted');
|
||||
await redisClient.del('blacklist-sip-gateway:124a5339-c62c-4075-9e19-f4de70a96597');
|
||||
|
||||
await waitFor(25);
|
||||
|
||||
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
||||
|
||||
Reference in New Issue
Block a user