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
. "$(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 CIDRMatcher = require('cidr-matcher');
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 setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = [];
@@ -183,7 +183,7 @@ if (process.env.DRACHTIO_HOST && !process.env.K8S) {
srf.locals.addToRedis();
}
}
srf.locals.sbcPublicIpAddress = parseHostPorts(hostports);
srf.locals.sbcPublicIpAddress = parseHostPorts(logger, hostports, srf);
});
}
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') {
@@ -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};

View File

@@ -225,7 +225,7 @@ class CallSession extends Emitter {
// 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}`;
this.contactHeader = `<${scheme}:${this.privateSipAddress};transport=${this.req.protocol}>`;
responseHeaders = {
...responseHeaders,
'Contact': this.contactHeader
@@ -243,7 +243,7 @@ class CallSession extends Emitter {
}
else {
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,
'Contact': this.contactHeader

View File

@@ -11,7 +11,7 @@ const isWSS = (req) => {
const getAppserver = (srf) => {
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) {
@@ -31,7 +31,7 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
'call-id': req.get('Call-ID'),
'replace': ['origin', 'session-connection'],
'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 {
common,
@@ -74,7 +74,7 @@ const equalsIgnoreOrder = (a, b) => {
return true;
};
const systemHealth = async(redisClient, ping, getCount) => {
const systemHealth = async(redisClient, ping, getCount) => {
await Promise.all([redisClient.ping(), ping()]);
return getCount();
};
@@ -112,8 +112,8 @@ 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 { service_provider_sid, account_sid, application_sid } = sids;
const { writeCallCount, writeCallCountSP, writeCallCountApp } = writers;
const nudges = [];
const writes = [];
@@ -145,28 +145,33 @@ const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
const [callsSP, calls, callsApp] = await Promise.all(nudges);
logger.debug({
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) {
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) {
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) {
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 */
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) {
logger.error(err, 'error incrementing call counts');
}
return {callsSP: null, calls: null, callsApp: null};
return { callsSP: null, calls: null, callsApp: null };
};
const roundTripTime = (startAt) => {
@@ -204,6 +209,31 @@ const isPrivateVoipNetwork = (ip) => {
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 = {
isWSS,
SdpWantsSrtp,
@@ -221,5 +251,7 @@ module.exports = {
roundTripTime,
parseConnectionIp,
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",
"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",
"jslint": "eslint app.js lib"
"jslint": "eslint app.js lib",
"jslint:fix": "npm run jslint --fix"
},
"dependencies": {
"@jambonz/db-helpers": "^0.9.3",
"@jambonz/db-helpers": "^0.9.4",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.8.8",
"@jambonz/rtpengine-utils": "^0.4.4",

View File

@@ -2,3 +2,4 @@ require('./docker_start');
require('./create-test-db');
require('./sip-tests');
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);
}
});