mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
9
Commits
v0.8.6-rc2
...
v0.9.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4153a141d7 | ||
|
|
e999b4e5e8 | ||
|
|
0e2285c4b8 | ||
|
|
f9ae466709 | ||
|
|
831005f7a1 | ||
|
|
a46ad934a8 | ||
|
|
d200799997 | ||
|
|
2fef169e92 | ||
|
|
5829ecdf4c |
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npm run jslint
|
||||
#npm run jslint
|
||||
|
||||
@@ -145,11 +145,28 @@ if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
||||
const matcher = new CIDRMatcher(cidrs);
|
||||
|
||||
srf.connect({host: process.env.DRACHTIO_HOST, port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET });
|
||||
srf.on('connect', (err, hp) => {
|
||||
srf.on('connect', (err, hp, version, localHostports) => {
|
||||
if (err) return this.logger.error({err}, 'Error connecting to drachtio server');
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
let addedPrivateIp = false;
|
||||
logger.info(`connected to drachtio ${version} listening on ${hp}, local hostports: ${localHostports}`);
|
||||
|
||||
const hostports = hp.split(',');
|
||||
|
||||
if (localHostports) {
|
||||
const locals = localHostports.split(',');
|
||||
for (const hp of locals) {
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||
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();
|
||||
addedPrivateIp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const hp of hostports) {
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||
if (arr && 'udp' === arr[1] && !matcher.contains(arr[2])) {
|
||||
@@ -157,7 +174,7 @@ if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
||||
srf.locals.sipAddress = arr[2];
|
||||
if (!process.env.SBC_ACCOUNT_SID) addSbcAddress(arr[2]);
|
||||
}
|
||||
else if (arr && 'tcp' === arr[1] && matcher.contains(arr[2])) {
|
||||
else if (!addedPrivateIp && 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;
|
||||
@@ -166,6 +183,7 @@ if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
||||
srf.locals.addToRedis();
|
||||
}
|
||||
}
|
||||
srf.locals.sbcPublicIpAddress = parseHostPorts(hostports);
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -173,13 +191,13 @@ else {
|
||||
logger.info(`listening in outbound mode on port ${process.env.DRACHTIO_PORT}`);
|
||||
});
|
||||
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
|
||||
srf.on('connect', (err, hp) => {
|
||||
srf.on('connect', (err, hp, version, localHostports) => {
|
||||
if (err) return this.logger.error({err}, 'Error connecting to drachtio server');
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
logger.info(`connected to drachtio ${version} listening on ${hp}, local hostports: ${localHostports}`);
|
||||
|
||||
if (process.env.K8S_FEATURE_SERVER_TRANSPORT === 'tcp') {
|
||||
const matcher = new CIDRMatcher(['192.168.0.0/24', '172.16.0.0/16', '10.0.0.0/8']);
|
||||
const hostports = hp.split(',');
|
||||
const hostports = localHostports ? localHostports.split(',') : hp.split(',');
|
||||
for (const hp of hostports) {
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||
if (arr && matcher.contains(arr[2])) {
|
||||
@@ -189,6 +207,7 @@ else {
|
||||
}
|
||||
}
|
||||
}
|
||||
srf.locals.sbcPublicIpAddress = parseHostPorts(hp.split(','));
|
||||
});
|
||||
}
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
@@ -345,4 +364,40 @@ function handle(removeFromSet, setName, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
const parseHostPorts = (hostports) => {
|
||||
let obj = {};
|
||||
for (const hp of hostports) {
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||
if (arr) {
|
||||
const ipv4 = arr[2];
|
||||
const port = arr[3];
|
||||
switch (arr[1]) {
|
||||
case 'udp':
|
||||
obj = {
|
||||
...obj,
|
||||
udp: `${ipv4}:${port}`
|
||||
};
|
||||
break;
|
||||
case 'tls':
|
||||
obj = {
|
||||
...obj,
|
||||
tls: `${ipv4}:${port}`
|
||||
};
|
||||
break;
|
||||
case 'wss':
|
||||
obj = {
|
||||
...obj,
|
||||
wss: `${ipv4}:${port}`
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!obj.tls) {
|
||||
obj.tls = `${srf.locals.sipAddress}:5061`;
|
||||
}
|
||||
}
|
||||
logger.info({obj}, 'sip endpoints');
|
||||
return obj;
|
||||
};
|
||||
|
||||
module.exports = {srf, logger};
|
||||
|
||||
+59
-19
@@ -166,6 +166,8 @@ class CallSession extends Emitter {
|
||||
const obj = parseUri(this.req.uri);
|
||||
let proxy, host, uri;
|
||||
|
||||
const scheme = obj.scheme;
|
||||
|
||||
// replace host part of uri if its an ipv4 address, leave it otherwise
|
||||
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(obj.host)) {
|
||||
debug(`replacing host: was ${obj.host} is ${featureServer}`);
|
||||
@@ -176,8 +178,8 @@ class CallSession extends Emitter {
|
||||
host = obj.host;
|
||||
proxy = `sip:${featureServer}`;
|
||||
}
|
||||
if (obj.user) uri = `${obj.scheme}:${obj.user}@${host}`;
|
||||
else uri = `${obj.scheme}:${host}`;
|
||||
if (obj.user) uri = `${scheme}:${obj.user}@${host}`;
|
||||
else uri = `${scheme}:${host}`;
|
||||
this.logger.info(`uri will be: ${uri}, proxy ${proxy}`);
|
||||
|
||||
try {
|
||||
@@ -213,21 +215,39 @@ class CallSession extends Emitter {
|
||||
createSiprecBody(headers, response.sdp, this.xml.type, this.xml.content) :
|
||||
response.sdp;
|
||||
|
||||
const responseHeaders = {};
|
||||
if (this.req.locals.carrier) {
|
||||
Object.assign(headers, {
|
||||
'X-Originating-Carrier': this.req.locals.carrier,
|
||||
'X-Voip-Carrier-Sid': this.req.locals.voip_carrier_sid
|
||||
});
|
||||
}
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
|
||||
// set Contact header based on scenario, and transport protocol
|
||||
let responseHeaders = {};
|
||||
if (isPrivateVoipNetwork(this.req.source_address)) {
|
||||
this.contactHeader = `<${scheme}:${this.privateSipAddress}>;transport=${this.req.protocol}`;
|
||||
responseHeaders = {
|
||||
...responseHeaders,
|
||||
'Contact': this.contactHeader
|
||||
};
|
||||
} else if (this.req.locals.msTeamsTenantFqdn) {
|
||||
Object.assign(headers, {'X-MS-Teams-Tenant-FQDN': this.req.locals.msTeamsTenantFqdn});
|
||||
|
||||
// for Microsoft Teams the Contact header must include the tenant FQDN
|
||||
Object.assign(responseHeaders, {
|
||||
this.contactHeader = `sip:${this.req.locals.msTeamsTenantFqdn}`;
|
||||
responseHeaders = {
|
||||
...responseHeaders,
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK',
|
||||
Contact: `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
});
|
||||
Contact: this.contactHeader
|
||||
};
|
||||
}
|
||||
else {
|
||||
const hostport = this.srf.locals.sbcPublicIpAddress[this.req.protocol];
|
||||
this.contactHeader = `<${scheme}:${hostport}>;transport=${this.req.protocol}`;
|
||||
responseHeaders = {
|
||||
...responseHeaders,
|
||||
'Contact': this.contactHeader
|
||||
};
|
||||
}
|
||||
if (this.req.locals.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.locals.application_sid});
|
||||
@@ -638,7 +658,12 @@ Duration=${payload.duration} `
|
||||
}
|
||||
else {
|
||||
this.logger.info('got a reINVITE with no SDP; just respond with our current offer');
|
||||
res.send(200, {body: dlg.local.sdp});
|
||||
res.send(200, {
|
||||
headers: {
|
||||
'Contact': this.contactHeader
|
||||
},
|
||||
body: dlg.local.sdp
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -691,7 +716,12 @@ Duration=${payload.duration} `
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
res.send(200, {body: response.sdp});
|
||||
res.send(200, {
|
||||
headers: {
|
||||
'Contact': this.contactHeader
|
||||
},
|
||||
body: response.sdp
|
||||
});
|
||||
} catch (err) {
|
||||
res.send(err.status || 500);
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
@@ -775,8 +805,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');
|
||||
@@ -784,14 +812,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');
|
||||
@@ -800,14 +832,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');
|
||||
@@ -815,14 +851,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');
|
||||
@@ -961,8 +1001,8 @@ Duration=${payload.duration} `
|
||||
const response = await this.uas.request({
|
||||
method: 'REFER',
|
||||
headers: {
|
||||
'Refer-To': stringifyUri(uri),
|
||||
'Referred-By': stringifyUri(u),
|
||||
'Refer-To': `<${stringifyUri(uri)}>`,
|
||||
'Referred-By': `<${stringifyUri(u)}>`,
|
||||
...customHeaders
|
||||
}
|
||||
});
|
||||
|
||||
+3
-2
@@ -22,7 +22,7 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
|
||||
/* Allow Feature server to inject DTMF to both leg except call from Teams*/
|
||||
/* Allow Feature server to inject DTMF to both leg except call from Teams */
|
||||
if (!teams) {
|
||||
dstOpts.flags.push('inject DTMF');
|
||||
srcOpts.flags.push('inject DTMF');
|
||||
@@ -30,7 +30,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const common = {
|
||||
'call-id': req.get('Call-ID'),
|
||||
'replace': ['origin', 'session-connection'],
|
||||
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
||||
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no',
|
||||
...(process.env.JAMBONES_ACCEPT_G729 && { codec: { mask: 'g729', transcode: 'pcmu'}})
|
||||
};
|
||||
return {
|
||||
common,
|
||||
|
||||
Generated
+3285
-2207
File diff suppressed because it is too large
Load Diff
+14
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "0.8.5",
|
||||
"version": "0.9.0",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -27,28 +27,28 @@
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.9.3",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@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/digest-utils": "^0.0.3",
|
||||
"@aws-sdk/client-sns": "^3.360.0",
|
||||
"@aws-sdk/client-auto-scaling": "^3.360.0",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/digest-utils": "^0.0.5",
|
||||
"@aws-sdk/client-sns": "^3.549.0",
|
||||
"@aws-sdk/client-auto-scaling": "^3.549.0",
|
||||
"bent": "^7.3.12",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.18.1",
|
||||
"pino": "^7.11.0",
|
||||
"drachtio-fn-b2b-sugar": "0.1.0",
|
||||
"drachtio-srf": "^4.5.38",
|
||||
"express": "^4.19.2",
|
||||
"pino": "^8.20.0",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"xml2js": "^0.4.23"
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"nyc": "^15.1.0",
|
||||
"tape": "^4.15.1"
|
||||
"tape": "^5.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user