Feat/improve parse host ports (#149)

* Move parseHostPorts to utils and cover with test suite

* fix tabs

* comment pre commit hook

* add test

* wip

* remove only

* fix 2 critical, 12 high vulnerabilities via npm audit fix

---------

Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
This commit is contained in:
Markus Frindt
2024-08-01 14:41:49 +02:00
committed by GitHub
parent b848ab99e1
commit c270c0df43
8 changed files with 1618 additions and 1457 deletions

View File

@@ -1,4 +1,5 @@
#!/bin/sh #!/bin/sh
. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/_/husky.sh"
#npm run jslint # npm audit fix || true
# npm run jslint

52
app.js
View File

@@ -37,7 +37,7 @@ const {
const StatsCollector = require('@jambonz/stats-collector'); const StatsCollector = require('@jambonz/stats-collector');
const CIDRMatcher = require('cidr-matcher'); const CIDRMatcher = require('cidr-matcher');
const stats = new StatsCollector(logger); const stats = new StatsCollector(logger);
const {equalsIgnoreOrder, createHealthCheckApp, systemHealth} = require('./lib/utils'); const {equalsIgnoreOrder, createHealthCheckApp, systemHealth, parseHostPorts} = require('./lib/utils');
const {LifeCycleEvents} = require('./lib/constants'); const {LifeCycleEvents} = require('./lib/constants');
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`; const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = []; const rtpServers = [];
@@ -183,7 +183,7 @@ if (process.env.DRACHTIO_HOST && !process.env.K8S) {
srf.locals.addToRedis(); srf.locals.addToRedis();
} }
} }
srf.locals.sbcPublicIpAddress = parseHostPorts(hostports); srf.locals.sbcPublicIpAddress = parseHostPorts(logger, hostports, srf);
}); });
} }
else { else {
@@ -207,7 +207,7 @@ else {
} }
} }
} }
srf.locals.sbcPublicIpAddress = parseHostPorts(hp.split(',')); srf.locals.sbcPublicIpAddress = parseHostPorts(logger, hp, srf);
}); });
} }
if (process.env.NODE_ENV === 'test') { if (process.env.NODE_ENV === 'test') {
@@ -364,50 +364,4 @@ function handle(removeFromSet, setName, signal) {
} }
} }
const parseHostPorts = (hostports) => {
let obj = {};
logger.info({hostports}, 'sip endpoints');
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 'tcp':
obj = {
...obj,
tcp: `${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`;
}
if (!obj.tcp) {
obj.tcp = `${srf.locals.sipAddress}:5060`;
}
}
logger.info({obj}, 'sip endpoints');
return obj;
};
module.exports = {srf, logger}; module.exports = {srf, logger};

View File

@@ -225,7 +225,7 @@ class CallSession extends Emitter {
// set Contact header based on scenario, and transport protocol // set Contact header based on scenario, and transport protocol
let responseHeaders = {}; let responseHeaders = {};
if (isPrivateVoipNetwork(this.req.source_address)) { if (isPrivateVoipNetwork(this.req.source_address)) {
this.contactHeader = `<${scheme}:${this.privateSipAddress}>;transport=${this.req.protocol}`; this.contactHeader = `<${scheme}:${this.privateSipAddress};transport=${this.req.protocol}>`;
responseHeaders = { responseHeaders = {
...responseHeaders, ...responseHeaders,
'Contact': this.contactHeader 'Contact': this.contactHeader
@@ -243,7 +243,7 @@ class CallSession extends Emitter {
} }
else { else {
const hostport = this.srf.locals.sbcPublicIpAddress[this.req.protocol]; const hostport = this.srf.locals.sbcPublicIpAddress[this.req.protocol];
this.contactHeader = `<${scheme}:${hostport}>;transport=${this.req.protocol}`; this.contactHeader = `<${scheme}:${hostport};transport=${this.req.protocol}>`;
responseHeaders = { responseHeaders = {
...responseHeaders, ...responseHeaders,
'Contact': this.contactHeader 'Contact': this.contactHeader

View File

@@ -11,7 +11,7 @@ const isWSS = (req) => {
const getAppserver = (srf) => { const getAppserver = (srf) => {
const len = srf.locals.featureServers.length; const len = srf.locals.featureServers.length;
return srf.locals.featureServers[ idx++ % len]; return srf.locals.featureServers[idx++ % len];
}; };
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) { function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
@@ -31,7 +31,7 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
'call-id': req.get('Call-ID'), 'call-id': req.get('Call-ID'),
'replace': ['origin', 'session-connection'], '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'}}) ...(process.env.JAMBONES_ACCEPT_G729 && { codec: { mask: 'g729', transcode: 'pcmu' } })
}; };
return { return {
common, common,
@@ -74,7 +74,7 @@ const equalsIgnoreOrder = (a, b) => {
return true; return true;
}; };
const systemHealth = async(redisClient, ping, getCount) => { const systemHealth = async(redisClient, ping, getCount) => {
await Promise.all([redisClient.ping(), ping()]); await Promise.all([redisClient.ping(), ping()]);
return getCount(); return getCount();
}; };
@@ -112,8 +112,8 @@ const createHealthCheckApp = (port, logger) => {
}; };
const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => { const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
const {service_provider_sid, account_sid, application_sid} = sids; const { service_provider_sid, account_sid, application_sid } = sids;
const {writeCallCount, writeCallCountSP, writeCallCountApp} = writers; const { writeCallCount, writeCallCountSP, writeCallCountApp } = writers;
const nudges = []; const nudges = [];
const writes = []; const writes = [];
@@ -145,28 +145,33 @@ const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
const [callsSP, calls, callsApp] = await Promise.all(nudges); const [callsSP, calls, callsApp] = await Promise.all(nudges);
logger.debug({ logger.debug({
calls, callsSP, callsApp, calls, callsSP, callsApp,
service_provider_sid, account_sid, application_sid}, 'call counts after adjustment'); service_provider_sid, account_sid, application_sid
}, 'call counts after adjustment');
if (process.env.JAMBONES_TRACK_SP_CALLS) { if (process.env.JAMBONES_TRACK_SP_CALLS) {
writes.push(writeCallCountSP({service_provider_sid, calls_in_progress: callsSP})); writes.push(writeCallCountSP({ service_provider_sid, calls_in_progress: callsSP }));
} }
if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) { if (process.env.JAMBONES_TRACK_ACCOUNT_CALLS || process.env.JAMBONES_HOSTING) {
writes.push(writeCallCount({service_provider_sid, account_sid, calls_in_progress: calls})); writes.push(writeCallCount({ service_provider_sid, account_sid, calls_in_progress: calls }));
} }
if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) { if (process.env.JAMBONES_TRACK_APP_CALLS && application_sid) {
writes.push(writeCallCountApp({service_provider_sid, account_sid, application_sid, calls_in_progress: callsApp})); writes.push(writeCallCountApp({
service_provider_sid,
account_sid, application_sid,
calls_in_progress: callsApp
}));
} }
/* write the call counts to the database */ /* write the call counts to the database */
Promise.all(writes).catch((err) => logger.error({err}, 'Error writing call counts')); Promise.all(writes).catch((err) => logger.error({ err }, 'Error writing call counts'));
return {callsSP, calls, callsApp}; return { callsSP, calls, callsApp };
} catch (err) { } catch (err) {
logger.error(err, 'error incrementing call counts'); logger.error(err, 'error incrementing call counts');
} }
return {callsSP: null, calls: null, callsApp: null}; return { callsSP: null, calls: null, callsApp: null };
}; };
const roundTripTime = (startAt) => { const roundTripTime = (startAt) => {
@@ -204,6 +209,31 @@ const isPrivateVoipNetwork = (ip) => {
return false; return false;
}; };
/**
* @param hostports can be a string or an array of hostports
*/
const parseHostPorts = (logger, hostports, srf) => {
typeof hostports === 'string' && (hostports = hostports.split(','));
const obj = {};
for (const hp of hostports) {
const [, protocol, ipv4, port] = hp.match(/^(.*)\/(.*):(\d+)$/);
if (protocol && ipv4 && port) {
obj[protocol] = `${ipv4}:${port}`;
}
}
if (!obj.tls) {
obj.tls = `${srf.locals.sipAddress}:5061`;
}
if (!obj.tcp) {
obj.tcp = `${srf.locals.sipAddress}:5060`;
}
logger.info({ obj }, 'sip endpoints');
return obj;
};
module.exports = { module.exports = {
isWSS, isWSS,
SdpWantsSrtp, SdpWantsSrtp,
@@ -221,5 +251,7 @@ module.exports = {
roundTripTime, roundTripTime,
parseConnectionIp, parseConnectionIp,
isMSTeamsCIDR, isMSTeamsCIDR,
isPrivateVoipNetwork isPrivateVoipNetwork,
parseHostPorts
}; };

2863
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,10 +22,11 @@
"start": "node app", "start": "node app",
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 JWT_SECRET=foobarbazzle 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 HTTP_PORT=3050 JAMBONES_NETWORK_CIDR='127.0.0.1/32' JAMBONES_HOSTING=1 JWT_SECRET=foobarbazzle 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/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test", "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib" "jslint": "eslint app.js lib",
"jslint:fix": "npm run jslint --fix"
}, },
"dependencies": { "dependencies": {
"@jambonz/db-helpers": "^0.9.3", "@jambonz/db-helpers": "^0.9.4",
"@jambonz/http-health-check": "^0.0.1", "@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.8.8", "@jambonz/realtimedb-helpers": "^0.8.8",
"@jambonz/rtpengine-utils": "^0.4.4", "@jambonz/rtpengine-utils": "^0.4.4",

View File

@@ -2,3 +2,4 @@ require('./docker_start');
require('./create-test-db'); require('./create-test-db');
require('./sip-tests'); require('./sip-tests');
require('./docker_stop'); require('./docker_stop');
require('./utils');

89
test/utils.js Normal file
View File

@@ -0,0 +1,89 @@
const test = require('tape');
const { parseHostPorts } = require('../lib/utils');
const { parseUri } = require('drachtio-srf');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
const hostports = "tls/3.70.141.74:5061,wss/3.70.141.74:8443,tcp/3.70.141.74:5060,udp/3.70.141.74:5060";
const hostportsNoTls = "wss/3.70.141.74:8443,tcp/3.70.141.74:5060,udp/3.70.141.74:5060";
const logger = { info: (args) => console.log(args) };
const srf = {
locals: {
sipAddress: '127.0.0.1'
}
};
test('utils tests - parseHostPorts', async (t) => {
try {
let obj = parseHostPorts(logger, hostports, srf);
const expected = {
tls: '3.70.141.74:5061',
wss: '3.70.141.74:8443',
tcp: '3.70.141.74:5060',
udp: '3.70.141.74:5060'
};
t.ok(obj.tls === expected.tls, 'sip endpoint tls');
t.ok(obj.wss === expected.wss, 'sip endpoint wss');
t.ok(obj.tcp === expected.tcp, 'sip endpoint tcp');
t.ok(obj.udp === expected.udp, 'sip endpoint udp');
obj = parseHostPorts(logger, hostportsNoTls.split(','), srf);
t.ok(obj.tls === '127.0.0.1:5061', 'sip endpoint tls');
t.end();
} catch (err) {
console.log(`error received: ${err}`);
t.error(err);
}
});
test('utils tests - parse URI user', async (t) => {
try {
let invalidUri = "sip:@202660.tenios.com";
const req = {
getParsedHeader: () => ({ uri: invalidUri })
};
const referTo = req.getParsedHeader('Refer-To');
let uri = parseUri(referTo.uri);
const expected = {
family: "ipv4",
scheme: "sip",
user: "",
password: undefined,
host: "202660.tenios.com",
port: NaN,
params: {},
headers: {},
}
t.ok(uri.family === expected.family, 'family eq ipv4');
t.ok(uri.scheme === expected.scheme, 'scheme eq sip');
t.ok(uri.password === expected.password, 'pw eq undefined');
t.ok(uri.host === expected.host, 'host eq 202660.tenios.com');
t.ok(uri.user === "", 'user eq empty string');
t.ok(isNaN(uri.port), 'port eq NaN');
t.ok(typeof uri.params === 'object', 'params eq object');
t.ok(typeof uri.headers === 'object', 'headers eq object');
invalidUri = "<sip:@202660.tenios.com>";
uri = parseUri(invalidUri);
/* TODO: uri can be undefined - check these conditions in call-session */
t.ok(uri === undefined, 'uri is undefined');
const validUri = "<sip:+49221578952870@202660.tenios.com>";
t.end();
} catch (err) {
console.log(`error received: ${err}`);
t.error(err);
}
});