mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
479d6f7405 |
@@ -54,10 +54,7 @@ const {createSet, retrieveSet, addToSet, removeFromSet, incrKey, decrKey} = requ
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
|
||||
emitter: stats,
|
||||
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22224
|
||||
});
|
||||
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {emitter: stats});
|
||||
srf.locals = {...srf.locals,
|
||||
stats,
|
||||
queryCdrs,
|
||||
@@ -99,6 +96,7 @@ if (process.env.DRACHTIO_HOST) {
|
||||
srf.on('connect', (err, hp) => {
|
||||
if (err) return this.logger.error({err}, 'Error connecting to drachtio server');
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
if (process.env.SBC_ACCOUNT_SID) return;
|
||||
|
||||
const hostports = hp.split(',');
|
||||
for (const hp of hostports) {
|
||||
@@ -106,12 +104,11 @@ if (process.env.DRACHTIO_HOST) {
|
||||
if (arr && 'udp' === arr[1] && !matcher.contains(arr[2])) {
|
||||
logger.info(`adding sbc public address to database: ${arr[2]}`);
|
||||
srf.locals.sipAddress = arr[2];
|
||||
if (!process.env.SBC_ACCOUNT_SID) addSbcAddress(arr[2]);
|
||||
addSbcAddress(arr[2]);
|
||||
}
|
||||
else if (arr && 'tcp' === arr[1] && matcher.contains(arr[2])) {
|
||||
const hostport = `${arr[2]}:${arr[3]}`;
|
||||
logger.info(`adding sbc private address to redis: ${hostport}`);
|
||||
srf.locals.privateSipAddress = hostport;
|
||||
srf.locals.addToRedis = () => addToSet(setName, hostport);
|
||||
srf.locals.removeFromRedis = () => removeFromSet(setName, hostport);
|
||||
srf.locals.addToRedis();
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"DTLS": "off",
|
||||
"SDES": "off",
|
||||
"ICE": "remove",
|
||||
"flags": ["media handover", "port latching"],
|
||||
"flags": ["media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["require"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
}
|
||||
+9
-123
@@ -39,10 +39,6 @@ class CallSession extends Emitter {
|
||||
return !!this.req.locals.msTeamsTenantFqdn;
|
||||
}
|
||||
|
||||
get privateSipAddress() {
|
||||
return this.srf.locals.privateSipAddress;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.logger.info('inbound call accepted for routing');
|
||||
const engine = this.getRtpEngine();
|
||||
@@ -53,26 +49,10 @@ class CallSession extends Emitter {
|
||||
return this.res.send(480);
|
||||
}
|
||||
debug(`got engine: ${JSON.stringify(engine)}`);
|
||||
const {
|
||||
offer,
|
||||
answer,
|
||||
del,
|
||||
blockMedia,
|
||||
unblockMedia,
|
||||
blockDTMF,
|
||||
unblockDTMF,
|
||||
subscribeDTMF,
|
||||
unsubscribeDTMF
|
||||
} = engine;
|
||||
const {offer, answer, del} = engine;
|
||||
this.offer = offer;
|
||||
this.answer = answer;
|
||||
this.del = del;
|
||||
this.blockMedia = blockMedia;
|
||||
this.unblockMedia = unblockMedia;
|
||||
this.blockDTMF = blockDTMF;
|
||||
this.unblockDTMF = unblockDTMF;
|
||||
this.subscribeDTMF = subscribeDTMF;
|
||||
this.unsubscribeDTMF = unsubscribeDTMF;
|
||||
|
||||
const featureServer = await this.getFeatureServer();
|
||||
if (!featureServer) {
|
||||
@@ -118,15 +98,13 @@ class CallSession extends Emitter {
|
||||
}
|
||||
|
||||
// now send the INVITE in towards the feature servers
|
||||
let headers = {
|
||||
const headers = {
|
||||
'From': createBLegFromHeader(this.req),
|
||||
'To': this.req.get('To'),
|
||||
'X-Account-Sid': this.req.locals.account_sid,
|
||||
'X-CID': this.req.get('Call-ID'),
|
||||
'X-Forwarded-For': `${this.req.source_address}`
|
||||
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
||||
};
|
||||
if (this.privateSipAddress) headers = {...headers, Contact: `<sip:${this.privateSipAddress}>`};
|
||||
|
||||
const responseHeaders = {};
|
||||
if (this.req.locals.carrier) Object.assign(headers, {'X-Originating-Carrier': this.req.locals.carrier});
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
@@ -164,7 +142,7 @@ class CallSession extends Emitter {
|
||||
'-Max-Forwards',
|
||||
'-Record-Route',
|
||||
'-Session-Expires',
|
||||
'-X-Subspace-Forwarded-For'
|
||||
'-X-Forwarded-For'
|
||||
],
|
||||
proxyResponseHeaders: ['all'],
|
||||
localSdpB: response.sdp,
|
||||
@@ -208,15 +186,12 @@ class CallSession extends Emitter {
|
||||
}
|
||||
|
||||
_setDlgHandlers(dlg) {
|
||||
const {callId} = dlg.sip;
|
||||
this.activeCallIds.set(callId, this);
|
||||
this.subscribeDTMF(this.logger, callId, this.rtpEngineOpts.uas.tag,
|
||||
this._onDTMF.bind(this));
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
dlg.on('destroy', () => {
|
||||
debug('call ended with normal termination');
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(callId);
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
|
||||
@@ -245,7 +220,7 @@ class CallSession extends Emitter {
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
dlg.other.destroy().catch((e) => {});
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
|
||||
if (process.env.JAMBONES_HOSTING) {
|
||||
this.decrKey(this.callCountKey)
|
||||
.then((count) => this.logger.debug({key: this.callCountKey},
|
||||
@@ -269,49 +244,14 @@ class CallSession extends Emitter {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag,
|
||||
this._onDTMF.bind(this, uac));
|
||||
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
uac.on('modify', this._onReinvite.bind(this, uac));
|
||||
|
||||
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
||||
uas.on('refer', this._onRefer.bind(this, uas));
|
||||
|
||||
uas.on('info', this._onInfo.bind(this, uas));
|
||||
uac.on('info', this._onInfo.bind(this, uac));
|
||||
|
||||
// default forwarding of other request types
|
||||
forwardInDialogRequests(uas, ['notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
async _onDTMF(dlg, payload) {
|
||||
this.logger.info({payload}, '_onDTMF');
|
||||
try {
|
||||
let dtmf;
|
||||
switch (payload.event) {
|
||||
case 10:
|
||||
dtmf = '*';
|
||||
break;
|
||||
case 11:
|
||||
dtmf = '#';
|
||||
break;
|
||||
default:
|
||||
dtmf = '' + payload.event;
|
||||
break;
|
||||
}
|
||||
await dlg.request({
|
||||
method: 'INFO',
|
||||
headers: {
|
||||
'Content-Type': 'application/dtmf-relay'
|
||||
},
|
||||
body: `Signal=${dtmf}
|
||||
Duration=${payload.duration} `
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Error sending INFO application/dtmf-relay');
|
||||
}
|
||||
forwardInDialogRequests(uas, ['info', 'notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,8 +288,6 @@ Duration=${payload.duration} `
|
||||
});
|
||||
}
|
||||
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
|
||||
const uas = await this.srf.createUAS(req, res, {
|
||||
localSdp: response.sdp,
|
||||
headers
|
||||
@@ -376,7 +314,6 @@ Duration=${payload.duration} `
|
||||
res.send(200, {body: dlg.local.sdp});
|
||||
return;
|
||||
}
|
||||
const reason = req.get('X-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;
|
||||
@@ -390,23 +327,13 @@ Duration=${payload.duration} `
|
||||
direction,
|
||||
sdp: req.body,
|
||||
};
|
||||
if (reason) 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)}`);
|
||||
}
|
||||
|
||||
/* if this is a re-invite from the FS to change media anchoring, avoid sending the reinvite out */
|
||||
let sdp;
|
||||
if (reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason)) {
|
||||
this.logger.info(`got a reinvite from FS to ${reason}`);
|
||||
sdp = dlg.other.remote.sdp;
|
||||
}
|
||||
else {
|
||||
sdp = await dlg.other.modify(response.sdp);
|
||||
}
|
||||
const sdp = await dlg.other.modify(response.sdp);
|
||||
opts = {
|
||||
...this.rtpEngineOpts.common,
|
||||
...answerMedia,
|
||||
@@ -425,46 +352,6 @@ Duration=${payload.duration} `
|
||||
}
|
||||
}
|
||||
|
||||
async _onInfo(dlg, req, res) {
|
||||
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
|
||||
try {
|
||||
if (dlg.type === 'uac' && req.has('X-Reason')) {
|
||||
const reason = req.get('X-Reason');
|
||||
const opts = {
|
||||
...this.rtpEngineOpts.common,
|
||||
flags: ['reset'],
|
||||
'from-tag': fromTag
|
||||
};
|
||||
this.logger.info(`_onInfo: got request ${reason}`);
|
||||
res.send(200);
|
||||
|
||||
if (reason.startsWith('mute')) {
|
||||
const response = Promise.all([this.blockMedia(opts), this.blockDTMF(opts)]);
|
||||
this.logger.info({response}, `_onInfo: response to rtpengine command for ${reason}`);
|
||||
}
|
||||
else if (reason.startsWith('unmute')) {
|
||||
const response = Promise.all([this.unblockMedia(opts), this.unblockDTMF(opts)]);
|
||||
this.logger.info({response}, `_onInfo: response to rtpengine command for ${reason}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const immutableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'max-forwards', 'content-length'];
|
||||
const headers = {};
|
||||
Object.keys(req.headers).forEach((h) => {
|
||||
if (!immutableHdrs.includes(h)) headers[h] = req.headers[h];
|
||||
});
|
||||
const response = await dlg.other.request({method: 'INFO', headers, body: req.body});
|
||||
const responseHeaders = {};
|
||||
if (response.has('Content-Type')) {
|
||||
Object.assign(responseHeaders, {'Content-Type': response.get('Content-Type')});
|
||||
}
|
||||
res.send(response.status, {headers: responseHeaders, body: response.body});
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, `Error handing INFO request on ${dlg.type} leg`);
|
||||
}
|
||||
}
|
||||
|
||||
async _onFeatureServerTransfer(dlg, req, res) {
|
||||
try {
|
||||
const referTo = req.getParsedHeader('Refer-To');
|
||||
@@ -564,7 +451,6 @@ Duration=${payload.duration} `
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('successfully connected new call leg for REFER');
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
this.referInvite = null;
|
||||
sendNotify(this.uas, '200 OK');
|
||||
this.uas.destroy();
|
||||
|
||||
+15
-8
@@ -75,14 +75,21 @@ module.exports = function(srf, logger) {
|
||||
req.locals = req.locals || {};
|
||||
|
||||
/* check if forwarded by a proxy that applied an X-Forwarded-For Header */
|
||||
if (req.has('X-Subspace-Forwarded-For')) {
|
||||
const original_source_address = req.get('X-Subspace-Forwarded-For');
|
||||
logger.info({
|
||||
callId: req.get('Call-ID'),
|
||||
original_source_address,
|
||||
proxy_source_address: req.source_address,
|
||||
}, 'overwriting source address for proxied SIP INVITE');
|
||||
req.source_address = original_source_address;
|
||||
if (req.has('X-Forwarded-For')) {
|
||||
const arr = /^([^:]*)(?::(.*))?$/.exec(req.get('X-Forwarded-For'));
|
||||
if (arr) {
|
||||
const original_source_address = arr[1];
|
||||
const original_source_port = arr[2] || req.source_port;
|
||||
logger({
|
||||
callId: req.get('Call-ID'),
|
||||
original_source_address,
|
||||
original_source_port,
|
||||
proxy_source_address: req.source_address,
|
||||
proxy_source_port: req.source_port
|
||||
}, 'overwriting source address and port for proxied SIP INVITE');
|
||||
req.source_address = original_source_address;
|
||||
req.source_port = original_source_port;
|
||||
}
|
||||
}
|
||||
req.locals.cdr = initCdr(req);
|
||||
const callId = req.get('Call-ID');
|
||||
|
||||
Generated
+327
-144
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "0.6.7-rc5",
|
||||
"version": "0.3.6",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app",
|
||||
"test": "NODE_ENV=test JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
|
||||
"test": "NODE_ENV=test JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
@@ -28,7 +28,7 @@
|
||||
"@jambonz/db-helpers": "^0.6.12",
|
||||
"@jambonz/http-authenticator": "^0.2.0",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.8",
|
||||
"@jambonz/rtpengine-utils": "^0.1.17",
|
||||
"@jambonz/rtpengine-utils": "^0.1.12",
|
||||
"@jambonz/stats-collector": "^0.1.5",
|
||||
"@jambonz/time-series": "^0.1.5",
|
||||
"aws-sdk": "^2.848.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.1",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.4.55",
|
||||
"drachtio-srf": "^4.4.49",
|
||||
"pino": "^6.8.0",
|
||||
"rtpengine-client": "^0.2.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user