mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 18:44:38 +00:00
Compare commits
80
Commits
v0.8.3-1
...
v0.9.5-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6771cbdefc | ||
|
|
e422c2ed9c | ||
|
|
fd5e2f1a6c | ||
|
|
5cc37265a9 | ||
|
|
1a9c438321 | ||
|
|
fb62fd9bf4 | ||
|
|
e02e0fa8c1 | ||
|
|
dd198105e3 | ||
|
|
a34491a356 | ||
|
|
3a467921a6 | ||
|
|
9cb75a0b6b | ||
|
|
28dae50202 | ||
|
|
0b45e766b9 | ||
|
|
7380457b5a | ||
|
|
9feb6f3c8f | ||
|
|
c449feeb9c | ||
|
|
b2abe9891e | ||
|
|
a89b54b19e | ||
|
|
ab0f3ed2b5 | ||
|
|
d8ae824559 | ||
|
|
334db6f84f | ||
|
|
c762accce8 | ||
|
|
c6c63e26da | ||
|
|
916d577b75 | ||
|
|
b976a62a60 | ||
|
|
5e2369e5e8 | ||
|
|
41528db630 | ||
|
|
c22104fbe2 | ||
|
|
3701e20295 | ||
|
|
821275cb67 | ||
|
|
f5ac51a8f7 | ||
|
|
0cf90f37b8 | ||
|
|
62fb716236 | ||
|
|
b8fdf9f429 | ||
|
|
503aa73bc6 | ||
|
|
a7406ddb8a | ||
|
|
bf1c670b40 | ||
|
|
b21eaad1ff | ||
|
|
c671fbdbd0 | ||
|
|
73e3779eb1 | ||
|
|
9ad7dc76c7 | ||
|
|
1baf7fa824 | ||
|
|
d2807c69ed | ||
|
|
3e6dc35e86 | ||
|
|
16a4709b7d | ||
|
|
01adb5cbf0 | ||
|
|
49ba872f51 | ||
|
|
591b8f2f8a | ||
|
|
16ac56e50e | ||
|
|
9412790366 | ||
|
|
453b27d6e1 | ||
|
|
4ba3d5b2b0 | ||
|
|
bc3e2334ed | ||
|
|
d70a01578d | ||
|
|
eb0de5b0ee | ||
|
|
d684fbb59b | ||
|
|
d86d03c792 | ||
|
|
b93e220fef | ||
|
|
43ee944023 | ||
|
|
5a6a9b34d3 | ||
|
|
1b215ed77b | ||
|
|
0b141c55bf | ||
|
|
f7bd1f2bf5 | ||
|
|
454986edcd | ||
|
|
114d34def4 | ||
|
|
6cf59732fb | ||
|
|
7f2bce927d | ||
|
|
41eaa4286f | ||
|
|
9fe731ab65 | ||
|
|
0977dd16b3 | ||
|
|
33971ed6d8 | ||
|
|
6200f2b50a | ||
|
|
7e13c5559f | ||
|
|
4343b01505 | ||
|
|
81d7a9f1e6 | ||
|
|
695e44da29 | ||
|
|
9b1a70d4a5 | ||
|
|
db38269a77 | ||
|
|
337c24df41 | ||
|
|
554cdbcacf |
@@ -6,12 +6,17 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 20.x
|
||||
- run: npm ci
|
||||
- run: npm run jslint
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
- run: npm test
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
@@ -18,7 +20,7 @@ jobs:
|
||||
- name: prepare tag
|
||||
id: prepare_tag
|
||||
run: |
|
||||
IMAGE_ID=$GITHUB_REPOSITORY
|
||||
IMAGE_ID=jambonz/sbc-outbound
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npm run jslint
|
||||
npm run jslint
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Drachtio Communications Services, LLC
|
||||
Copyright (c) 2018-2024 FirstFive8, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -3,7 +3,12 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_USER &&
|
||||
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
if (process.env.JAMBONES_REDIS_SENTINELS) {
|
||||
assert.ok(process.env.JAMBONES_REDIS_SENTINEL_MASTER_NAME,
|
||||
'missing JAMBONES_REDIS_SENTINEL_MASTER_NAME env var, JAMBONES_REDIS_SENTINEL_PASSWORD env var is optional');
|
||||
} else {
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
}
|
||||
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
|
||||
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
||||
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR env var');
|
||||
@@ -45,7 +50,8 @@ const {
|
||||
lookupSipGatewaysByCarrier,
|
||||
lookupCarrierBySid,
|
||||
queryCallLimits,
|
||||
lookupCarrierByAccountLcr
|
||||
lookupCarrierByAccountLcr,
|
||||
lookupSystemInformation
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
port: process.env.JAMBONES_MYSQL_PORT || 3306,
|
||||
@@ -61,11 +67,11 @@ const {
|
||||
incrKey,
|
||||
decrKey,
|
||||
retrieveSet,
|
||||
isMemberOfSet
|
||||
} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
isMemberOfSet,
|
||||
addKey,
|
||||
deleteKey,
|
||||
retrieveKey
|
||||
} = require('@jambonz/realtimedb-helpers')({}, logger);
|
||||
|
||||
const activeCallIds = new Map();
|
||||
const Emitter = require('events');
|
||||
@@ -82,6 +88,7 @@ srf.locals = {...srf.locals,
|
||||
queryCdrs,
|
||||
activeCallIds,
|
||||
idleEmitter,
|
||||
privateNetworkCidr: process.env.PRIVATE_VOIP_NETWORK_CIDR || null,
|
||||
dbHelpers: {
|
||||
ping,
|
||||
lookupOutboundCarrierForAccount,
|
||||
@@ -92,9 +99,14 @@ srf.locals = {...srf.locals,
|
||||
lookupSipGatewaysByCarrier,
|
||||
lookupCarrierBySid,
|
||||
queryCallLimits,
|
||||
lookupCarrierByAccountLcr
|
||||
lookupCarrierByAccountLcr,
|
||||
lookupSystemInformation
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
client: redisClient,
|
||||
addKey,
|
||||
deleteKey,
|
||||
retrieveKey,
|
||||
createHash,
|
||||
retrieveHash,
|
||||
incrKey,
|
||||
@@ -102,10 +114,7 @@ srf.locals = {...srf.locals,
|
||||
isMemberOfSet
|
||||
}
|
||||
};
|
||||
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, {
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
});
|
||||
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, redisClient);
|
||||
const ngProtocol = process.env.JAMBONES_NG_PROTOCOL || 'udp';
|
||||
const ngPort = process.env.RTPENGINE_PORT || ('udp' === ngProtocol ? 22222 : 8080);
|
||||
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
|
||||
@@ -184,10 +193,22 @@ if (process.env.K8S || process.env.HTTP_PORT) {
|
||||
});
|
||||
}
|
||||
if ('test' !== process.env.NODE_ENV) {
|
||||
/* update call stats periodically */
|
||||
setInterval(() => {
|
||||
/* update call stats periodically as well as definition of private network cidr */
|
||||
setInterval(async() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:outbound',
|
||||
`instance_id:${process.env.INSTANCE_ID || 0}`]);
|
||||
|
||||
const r = await lookupSystemInformation();
|
||||
if (r) {
|
||||
if (r.private_network_cidr !== srf.locals.privateNetworkCidr) {
|
||||
logger.info(`updating private network cidr from ${srf.locals.privateNetworkCidr} to ${r.private_network_cidr}`);
|
||||
srf.locals.privateNetworkCidr = r.private_network_cidr;
|
||||
}
|
||||
// Update system log level
|
||||
if (r.log_level) {
|
||||
logger.level = r.log_level;
|
||||
}
|
||||
}
|
||||
}, 20000);
|
||||
}
|
||||
|
||||
@@ -262,4 +283,4 @@ function handle(signal) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {srf};
|
||||
module.exports = {srf, logger};
|
||||
|
||||
+486
-148
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"MediaPath": {
|
||||
"NoMedia": "no-media",
|
||||
"PartialMedia": "partial-media",
|
||||
"FullMedia": "full-media"
|
||||
}
|
||||
}
|
||||
+40
-10
@@ -4,10 +4,10 @@ const Registrar = require('@jambonz/mw-registrar');
|
||||
const {selectHostPort, nudgeCallCounts} = require('./utils');
|
||||
const FS_UUID_SET_NAME = 'fsUUIDs';
|
||||
|
||||
module.exports = (srf, logger, opts) => {
|
||||
module.exports = (srf, logger, redisClient) => {
|
||||
const {incrKey, decrKey, isMemberOfSet} = srf.locals.realtimeDbHelpers;
|
||||
const {stats} = srf.locals;
|
||||
const registrar = new Registrar(opts);
|
||||
const registrar = new Registrar(logger, redisClient);
|
||||
const {
|
||||
lookupAccountCapacitiesBySid,
|
||||
lookupAccountBySid,
|
||||
@@ -17,8 +17,11 @@ module.exports = (srf, logger, opts) => {
|
||||
const initLocals = async(req, res, next) => {
|
||||
req.locals = req.locals || {};
|
||||
const callId = req.get('Call-ID');
|
||||
req.locals.nudge = 0;
|
||||
req.locals.callId = callId;
|
||||
req.locals.account_sid = req.get('X-Account-Sid');
|
||||
req.locals.application_sid = req.get('X-Application-Sid');
|
||||
req.locals.record_all_calls = req.get('X-Record-All-Calls');
|
||||
const traceId = req.locals.trace_id = req.get('X-Trace-ID');
|
||||
req.locals.logger = logger.child({
|
||||
callId,
|
||||
@@ -76,7 +79,11 @@ module.exports = (srf, logger, opts) => {
|
||||
});
|
||||
|
||||
try {
|
||||
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
|
||||
const account = await lookupAccountBySid(req.locals.account_sid);
|
||||
req.locals.account = account;
|
||||
if (account.enable_debug_log) {
|
||||
req.locals.logger.level = 'debug';
|
||||
}
|
||||
req.locals.service_provider_sid = req.locals.account.service_provider_sid;
|
||||
} catch (err) {
|
||||
req.locals.logger.error({err}, `Error looking up account sid ${req.locals.account_sid}`);
|
||||
@@ -102,10 +109,11 @@ module.exports = (srf, logger, opts) => {
|
||||
/* decrement count if INVITE is later rejected */
|
||||
res.once('end', async({status}) => {
|
||||
if (status > 200) {
|
||||
nudgeCallCounts(logger, {
|
||||
nudgeCallCounts(req, 'failure', {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
application_sid
|
||||
application_sid,
|
||||
callId: req.locals.callId
|
||||
}, decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||
.catch((err) => logger.error(err, 'Error decrementing call counts'));
|
||||
const tags = ['accepted:no', `sipStatus:${status}`];
|
||||
@@ -118,10 +126,11 @@ module.exports = (srf, logger, opts) => {
|
||||
});
|
||||
|
||||
/* increment the call count */
|
||||
const {callsSP, calls} = await nudgeCallCounts(logger, {
|
||||
const {callsSP, calls} = await nudgeCallCounts(req, 'init', {
|
||||
service_provider_sid,
|
||||
account_sid,
|
||||
application_sid
|
||||
application_sid,
|
||||
callId: req.locals.callId
|
||||
}, incrKey, {writeCallCountSP, writeCallCount, writeCallCountApp});
|
||||
|
||||
/* compare to account's limit, though avoid db hit when call count is low */
|
||||
@@ -165,6 +174,11 @@ module.exports = (srf, logger, opts) => {
|
||||
});
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
if (!account_limit && !sp_limit && process.env.JAMBONES_HOSTING) {
|
||||
logger.info(`checkLimits: no active subscription found for account ${account_sid}, rejecting call`);
|
||||
res.send(503, 'No Active Subscription');
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
if (process.env.JAMBONES_TRACK_SP_CALLS && sp_limit > 0 && callsSP > sp_limit) {
|
||||
logger.info({callsSP, sp_limit}, 'checkLimits: service provider limits exceeded');
|
||||
writeAlerts({
|
||||
@@ -222,7 +236,10 @@ module.exports = (srf, logger, opts) => {
|
||||
/* redirect to the correct SBC where this user is connected */
|
||||
const proxyAddress = selectHostPort(reg.sbcAddress, 'tcp');
|
||||
const redirectUri = `<sip:${proxyAddress[1]}>`;
|
||||
logger.info(`redirecting call to SBC at ${redirectUri}`);
|
||||
logger.info({
|
||||
myHostPort: req.server.hostport,
|
||||
registeredHostPort: reg.sbcAddress,
|
||||
}, `redirecting call to SBC at ${redirectUri}`);
|
||||
return res.send(302, {headers: {Contact: redirectUri}});
|
||||
}
|
||||
req.locals.registration = reg;
|
||||
@@ -243,9 +260,22 @@ module.exports = (srf, logger, opts) => {
|
||||
else if ('sip' === desiredRouting) {
|
||||
// call that needs to be forwarded to a sip endpoint
|
||||
logger.info(`forwarding call to sip endpoint ${req.uri}`);
|
||||
|
||||
// check if the domain is one of ours
|
||||
if (process.env.JAMBONES_LOCAL_SIP_DOMAINS) {
|
||||
const allowedDomains = process.env.JAMBONES_LOCAL_SIP_DOMAINS.split(',');
|
||||
const domain = uri.host;
|
||||
const isLoop = allowedDomains.some((allowed) =>
|
||||
domain === allowed.trim() || domain.endsWith(`.${allowed.trim()}`)
|
||||
);
|
||||
if (isLoop) {
|
||||
logger.info({ host: domain }, `returning 482 Loop Detected for attempt to send to: ${req.uri}`);
|
||||
res.send(482, 'Loop Detected');
|
||||
return req.srf.endSession(req);
|
||||
}
|
||||
}
|
||||
req.locals.target = 'forward';
|
||||
}
|
||||
else if ('phone' === desiredRouting) {
|
||||
} else if ('phone' === desiredRouting) {
|
||||
debug('sending call to LCR');
|
||||
req.locals.target = 'lcr';
|
||||
}
|
||||
|
||||
+182
-10
@@ -1,19 +1,32 @@
|
||||
const rtpCharacteristics = require('../data/rtp-transcoding');
|
||||
const srtpCharacteristics = require('../data/srtp-transcoding');
|
||||
const debug = require('debug')('jambonz:sbc-outbound');
|
||||
const CIDRMatcher = require('cidr-matcher');
|
||||
const dns = require('dns');
|
||||
const sdpTransform = require('sdp-transform');
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams) {
|
||||
const from = req.getParsedHeader('from');
|
||||
const rtpCopy = JSON.parse(JSON.stringify(rtpCharacteristics));
|
||||
const srtpCopy = JSON.parse(JSON.stringify(srtpCharacteristics));
|
||||
|
||||
if (padCrypto) {
|
||||
srtpCopy['default'].flags.push('SDES-pad');
|
||||
srtpCopy['teams'].flags.push('SDES-pad');
|
||||
}
|
||||
|
||||
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCopy;
|
||||
|
||||
/* webrtc clients (e.g. sipjs) send DMTF via SIP INFO */
|
||||
if ((srcIsUsingSrtp || dstIsUsingSrtp) && !teams) {
|
||||
dstOpts.flags.push('inject DTMF');
|
||||
srcOpts.flags.push('inject DTMF');
|
||||
/** Allow feature server to send DTMF to the call excepts call from/to teams */
|
||||
if (!teams) {
|
||||
if (!dstOpts.flags.includes('inject DTMF')) {
|
||||
dstOpts.flags.push('inject DTMF');
|
||||
}
|
||||
if (!srcOpts.flags.includes('inject DTMF')) {
|
||||
srcOpts.flags.push('inject DTMF');
|
||||
}
|
||||
}
|
||||
const common = {
|
||||
'call-id': req.get('Call-ID'),
|
||||
@@ -28,7 +41,11 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
},
|
||||
uac: {
|
||||
tag: null,
|
||||
mediaOpts: dstOpts
|
||||
mediaOpts: {
|
||||
...dstOpts,
|
||||
...(process.env.JAMBONES_CODEC_OFFER_WITH_ORDER &&
|
||||
{ codec: { offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','), strip: 'all' } }),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -115,13 +132,96 @@ const createHealthCheckApp = (port, logger) => {
|
||||
});
|
||||
};
|
||||
|
||||
const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
|
||||
const {service_provider_sid, account_sid, application_sid} = sids;
|
||||
/**
|
||||
* nudgeCallCounts - increment or decrement call counts in redis
|
||||
*
|
||||
* current nudge value
|
||||
* -----------------------------------------
|
||||
* why | -1 | 0 | 1 |
|
||||
* -----------------------------------------
|
||||
* init | no-op | +1 | N/A |
|
||||
* failure | N/A | -1 | -1 |
|
||||
* complete| N/A | N/A | -1 |
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const shouldNudge = (why, req) => {
|
||||
const {nudge, logger} = req.locals;
|
||||
let modifyCount = false;
|
||||
const originalNudge = nudge;
|
||||
|
||||
switch (why) {
|
||||
case 'init':
|
||||
if (nudge === 0) {
|
||||
// normal case: new call, increment call count
|
||||
req.locals.nudge = 1;
|
||||
modifyCount = true;
|
||||
}
|
||||
else if (nudge === -1) {
|
||||
// extremely quick cancel, don't increment call count
|
||||
req.locals.nudge = 0;
|
||||
}
|
||||
else {
|
||||
logger.info(`shouldNudge: unexpected nudge value ${nudge} for ${why}`);
|
||||
}
|
||||
break;
|
||||
case 'failure':
|
||||
if (nudge === 1) {
|
||||
// normal case of call failed for any reason, decrement call count
|
||||
req.locals.nudge = 0;
|
||||
modifyCount = true;
|
||||
}
|
||||
else if (nudge === 0) {
|
||||
// very quick failure dont decrement call count
|
||||
req.locals.nudge = -1;
|
||||
}
|
||||
else {
|
||||
logger.info(`shouldNudge: unexpected nudge value ${nudge} for ${why}`);
|
||||
}
|
||||
break;
|
||||
case 'complete':
|
||||
if (nudge === 1) {
|
||||
// normal case of call completed, decrement call count
|
||||
req.locals.nudge = 0;
|
||||
modifyCount = true;
|
||||
}
|
||||
else {
|
||||
logger.info(`shouldNudge: unexpected nudge value ${nudge} for ${why}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.info(`shouldNudge: unexpected why value ${why}`);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.info(`shouldNudge: '${why}': updating count: ${modifyCount}, nudge: ${originalNudge} -> ${req.locals.nudge}`);
|
||||
return modifyCount;
|
||||
};
|
||||
|
||||
const nudgeCallCounts = async(req, why, sids, nudgeOperator, writers) => {
|
||||
const {logger} = req.locals;
|
||||
const {service_provider_sid, account_sid, application_sid, callId} = sids;
|
||||
const {writeCallCount, writeCallCountSP, writeCallCountApp} = writers;
|
||||
const nudges = [];
|
||||
const writes = [];
|
||||
|
||||
logger.debug(sids, 'nudgeCallCounts');
|
||||
if (!shouldNudge(why, req)) {
|
||||
return {callsSP: null, calls: null, callsApp: null};
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_DEBUG_CALL_COUNTS) {
|
||||
const {srf} = require('..');
|
||||
const {addKey, deleteKey} = srf.locals.realtimeDbHelpers;
|
||||
|
||||
if (why === 'init') {
|
||||
// save for 3 days
|
||||
await addKey(`debug:outcalls:${account_sid}:${callId}`, new Date().toISOString(), 259200);
|
||||
}
|
||||
else {
|
||||
await deleteKey(`debug:outcalls:${account_sid}:${callId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.JAMBONES_TRACK_SP_CALLS) {
|
||||
const key = makeSPCallCountKey(service_provider_sid);
|
||||
@@ -175,6 +275,72 @@ const nudgeCallCounts = async(logger, sids, nudgeOperator, writers) => {
|
||||
return {callsSP: null, calls: null, callsApp: null};
|
||||
};
|
||||
|
||||
const isPrivateVoipNetwork = async(uri) => {
|
||||
const {srf, logger} = require('..');
|
||||
const {privateNetworkCidr} = srf.locals;
|
||||
|
||||
if (privateNetworkCidr) {
|
||||
try {
|
||||
const matcher = new CIDRMatcher(privateNetworkCidr.split(','));
|
||||
const arr = /sips?:.*@(.*?)(:\d+)?(;.*)$/.exec(uri);
|
||||
if (arr) {
|
||||
const input = arr[1];
|
||||
let addresses;
|
||||
if (input.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
|
||||
addresses = [input];
|
||||
} else {
|
||||
addresses = await dns.resolve4(input);
|
||||
}
|
||||
for (const ip of addresses) {
|
||||
if (matcher.contains(ip)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.info({err, privateNetworkCidr},
|
||||
'Error checking private network CIDR, probably misconfigured must be a comma separated list of CIDRs');
|
||||
}
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
const makeFullMediaReleaseKey = (callId) => {
|
||||
return `b_sdp:${callId}`;
|
||||
};
|
||||
const makePartnerFullMediaReleaseKey = (callId) => {
|
||||
return `a_sdp:${callId}`;
|
||||
};
|
||||
|
||||
function isValidDomainOrIP(input) {
|
||||
const domainRegex = /^(?!:\/\/)([a-zA-Z0-9.-]+)(:\d+)?$/;
|
||||
// eslint-disable-next-line max-len
|
||||
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(:\d+)?$/;
|
||||
|
||||
if (domainRegex.test(input) || ipRegex.test(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Invalid input
|
||||
}
|
||||
const removeVideoSdp = (sdp) => {
|
||||
const parsedSdp = sdpTransform.parse(sdp);
|
||||
// Filter out video media sections, keeping only non-video media
|
||||
parsedSdp.media = parsedSdp.media.filter((media) => media.type !== 'video');
|
||||
return sdpTransform.write(parsedSdp);
|
||||
};
|
||||
module.exports = {
|
||||
makeRtpEngineOpts,
|
||||
selectHostPort,
|
||||
@@ -184,5 +350,11 @@ module.exports = {
|
||||
equalsIgnoreOrder,
|
||||
systemHealth,
|
||||
createHealthCheckApp,
|
||||
nudgeCallCounts
|
||||
nudgeCallCounts,
|
||||
isPrivateVoipNetwork,
|
||||
isBlackListedSipGateway,
|
||||
makeFullMediaReleaseKey,
|
||||
makePartnerFullMediaReleaseKey,
|
||||
isValidDomainOrIP,
|
||||
removeVideoSdp
|
||||
};
|
||||
|
||||
Generated
+1347
-4577
File diff suppressed because it is too large
Load Diff
+18
-17
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "sbc-outbound",
|
||||
"version": "0.8.3",
|
||||
"version": "0.9.5",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"sip",
|
||||
@@ -24,30 +24,31 @@
|
||||
"start": "node app",
|
||||
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONES_HOSTING=1 JAMBONES_NETWORK_CIDR=127.0.0.1/32 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_TIME_SERIES_HOST=127.0.0.1 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 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 --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.9.0",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.1",
|
||||
"@babel/helpers": "^7.26.10",
|
||||
"@jambonz/db-helpers": "^0.9.12",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.2",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/siprec-client-utils": "^0.2.5",
|
||||
"@jambonz/stats-collector": "^0.1.8",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"@jambonz/mw-registrar": "0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.13",
|
||||
"@jambonz/rtpengine-utils": "^0.4.4",
|
||||
"@jambonz/siprec-client-utils": "^0.2.10",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.10",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"drachtio-fn-b2b-sugar": "^0.0.12",
|
||||
"drachtio-srf": "^4.5.21",
|
||||
"express": "^4.18.1",
|
||||
"pino": "^7.11.0",
|
||||
"sdp-transform": "^2.14.1"
|
||||
"drachtio-fn-b2b-sugar": "^0.2.1",
|
||||
"drachtio-srf": "^5.0.5",
|
||||
"express": "^4.19.2",
|
||||
"pino": "^8.20.0",
|
||||
"sdp-transform": "^2.15.0"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* SQLEditor (MySQL (2))*/
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS account_static_ips;
|
||||
@@ -13,6 +14,8 @@ DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS clients;
|
||||
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
|
||||
DROP TABLE IF EXISTS lcr;
|
||||
@@ -51,6 +54,8 @@ DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS google_custom_voices;
|
||||
|
||||
DROP TABLE IF EXISTS speech_credentials;
|
||||
|
||||
DROP TABLE IF EXISTS system_information;
|
||||
@@ -127,6 +132,19 @@ application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
username VARCHAR(64),
|
||||
password VARCHAR(1024),
|
||||
allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (client_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE dns_records
|
||||
(
|
||||
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -322,14 +340,27 @@ last_tested DATETIME,
|
||||
tts_tested_ok BOOLEAN,
|
||||
stt_tested_ok BOOLEAN,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
label VARCHAR(64),
|
||||
PRIMARY KEY (speech_credential_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE google_custom_voices
|
||||
(
|
||||
google_custom_voice_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
speech_credential_sid CHAR(36) NOT NULL,
|
||||
model VARCHAR(512) NOT NULL,
|
||||
reported_usage ENUM('REPORTED_USAGE_UNSPECIFIED','REALTIME','OFFLINE') DEFAULT 'REALTIME',
|
||||
name VARCHAR(64) NOT NULL,
|
||||
PRIMARY KEY (google_custom_voice_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE system_information
|
||||
(
|
||||
domain_name VARCHAR(255),
|
||||
sip_domain_name VARCHAR(255),
|
||||
monitoring_domain_name VARCHAR(255)
|
||||
monitoring_domain_name VARCHAR(255),
|
||||
private_network_cidr VARCHAR(8192),
|
||||
log_level ENUM('info', 'debug') NOT NULL DEFAULT 'info'
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
@@ -411,7 +442,7 @@ PRIMARY KEY (smpp_gateway_sid)
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(132) NOT NULL UNIQUE ,
|
||||
number VARCHAR(132) NOT NULL,
|
||||
voip_carrier_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
@@ -424,11 +455,14 @@ CREATE TABLE sip_gateways
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
port INTEGER COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
send_options_ping BOOLEAN NOT NULL DEFAULT 0,
|
||||
use_sips_scheme BOOLEAN NOT NULL DEFAULT 0,
|
||||
pad_crypto BOOLEAN NOT NULL DEFAULT 0,
|
||||
protocol ENUM('udp','tcp','tls', 'tls/srtp') DEFAULT 'udp' COMMENT 'Outbound call protocol',
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
@@ -465,10 +499,21 @@ messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
app_json TEXT,
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
speech_synthesis_voice VARCHAR(256),
|
||||
speech_synthesis_label VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
speech_recognizer_label VARCHAR(64),
|
||||
use_for_fallback_speech BOOLEAN DEFAULT false,
|
||||
fallback_speech_synthesis_vendor VARCHAR(64),
|
||||
fallback_speech_synthesis_language VARCHAR(12),
|
||||
fallback_speech_synthesis_voice VARCHAR(256),
|
||||
fallback_speech_synthesis_label VARCHAR(64),
|
||||
fallback_speech_recognizer_vendor VARCHAR(64),
|
||||
fallback_speech_recognizer_language VARCHAR(64),
|
||||
fallback_speech_recognizer_label VARCHAR(64),
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
record_all_calls BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (application_sid)
|
||||
) COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
@@ -506,6 +551,10 @@ subspace_client_secret VARCHAR(255),
|
||||
subspace_sip_teleport_id VARCHAR(255),
|
||||
subspace_sip_teleport_destinations VARCHAR(255),
|
||||
siprec_hook_sid CHAR(36),
|
||||
record_all_calls BOOLEAN NOT NULL DEFAULT false,
|
||||
record_format VARCHAR(16) NOT NULL DEFAULT 'mp3',
|
||||
bucket_credential VARCHAR(8192) COMMENT 'credential used to authenticate with storage service',
|
||||
enable_debug_log BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (account_sid)
|
||||
) COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
@@ -526,6 +575,9 @@ ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERE
|
||||
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX client_sid_idx ON clients (client_sid);
|
||||
ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
@@ -590,8 +642,6 @@ CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
||||
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||
|
||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
@@ -599,6 +649,10 @@ ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (ser
|
||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX google_custom_voice_sid_idx ON google_custom_voices (google_custom_voice_sid);
|
||||
CREATE INDEX speech_credential_sid_idx ON google_custom_voices (speech_credential_sid);
|
||||
ALTER TABLE google_custom_voices ADD FOREIGN KEY speech_credential_sid_idxfk (speech_credential_sid) REFERENCES speech_credentials (speech_credential_sid) ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX email_idx ON users (email);
|
||||
CREATE INDEX phone_idx ON users (phone);
|
||||
@@ -628,6 +682,8 @@ CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
||||
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE UNIQUE INDEX phone_numbers_unique_idx_voip_carrier_number ON phone_numbers (number,voip_carrier_sid);
|
||||
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX number_idx ON phone_numbers (number);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
ipv4_address: 172.39.0.2
|
||||
|
||||
sbc:
|
||||
image: drachtio/drachtio-server:latest
|
||||
image: drachtio/drachtio-server:0.8.26
|
||||
command: drachtio --contact "sip:*;transport=udp" --loglevel debug --sofia-loglevel 9
|
||||
ports:
|
||||
- "9060:9022/tcp"
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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@172.39.0.20 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@172.39.0.20>
|
||||
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: fff49e33-e771-403a-8c99-1780eabbc803
|
||||
X-Jambonz-Routing: sip
|
||||
Subject: uac-pcap-carrier-success
|
||||
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="180" optional="true">
|
||||
</recv>
|
||||
|
||||
<recv response="200" rtd="true" crlf="true">
|
||||
</recv>
|
||||
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip: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:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Subject: uac-pcap-carrier-success
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<!-- Play a pre-recorded PCAP file (RTP stream) -->
|
||||
<nop>
|
||||
<action>
|
||||
<exec play_pcap_audio="pcap/g711a.pcap"/>
|
||||
</action>
|
||||
</nop>
|
||||
|
||||
<!-- Pause briefly -->
|
||||
<pause milliseconds="2000"/>
|
||||
|
||||
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
BYE sip: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:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 2 BYE
|
||||
Max-Forwards: 70
|
||||
Subject: uac-pcap-carrier-success
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<recv response="200" crlf="true">
|
||||
</recv>
|
||||
|
||||
</scenario>
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||
|
||||
<!-- This program is free software; you can redistribute it and/or -->
|
||||
<!-- modify it under the terms of the GNU General Public License as -->
|
||||
<!-- published by the Free Software Foundation; either version 2 of the -->
|
||||
<!-- License, or (at your option) any later version. -->
|
||||
<!-- -->
|
||||
<!-- This program is distributed in the hope that it will be useful, -->
|
||||
<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
|
||||
<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -->
|
||||
<!-- GNU General Public License for more details. -->
|
||||
<!-- -->
|
||||
<!-- You should have received a copy of the GNU General Public License -->
|
||||
<!-- along with this program; if not, write to the -->
|
||||
<!-- Free Software Foundation, Inc., -->
|
||||
<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -->
|
||||
<!-- -->
|
||||
<!-- Sipp 'uac' scenario with pcap (rtp) play -->
|
||||
<!-- -->
|
||||
|
||||
<scenario name="UAC with media">
|
||||
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:john@foo.bar 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:john@foo.bar>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
Max-Forwards: 70
|
||||
Subject: uac-sip-uri-proxy
|
||||
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
|
||||
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
|
||||
X-Jambonz-Routing: sip
|
||||
X-SIP-Proxy: 172.39.0.20:5060
|
||||
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="180" optional="true">
|
||||
</recv>
|
||||
|
||||
<recv response="200">
|
||||
</recv>
|
||||
|
||||
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:john@foo.bar SIP/2.0
|
||||
[last_Via:]
|
||||
[last_From:]
|
||||
To: <sip:sip:john@jambonz.org>[peer_tag_param]
|
||||
[last_Call-ID:]
|
||||
CSeq: 2 ACK
|
||||
Subject:
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<!-- Play a pre-recorded PCAP file (RTP stream) -->
|
||||
<nop>
|
||||
<action>
|
||||
<exec play_pcap_audio="pcap/g711a.pcap"/>
|
||||
</action>
|
||||
</nop>
|
||||
|
||||
<!-- Pause briefly -->
|
||||
<pause milliseconds="2000"/>
|
||||
|
||||
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
BYE sip:john@foo.bar SIP/2.0
|
||||
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||
[last_From:]
|
||||
[last_To:]
|
||||
[last_Call-ID:]
|
||||
CSeq: 3 BYE
|
||||
Max-Forwards: 70
|
||||
Subject: uac-sip-uri-proxy
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<recv response="200" crlf="true">
|
||||
</recv>
|
||||
|
||||
<!-- definition of the response time repartition table (unit is ms) -->
|
||||
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
|
||||
|
||||
<!-- definition of the call length repartition table (unit is ms) -->
|
||||
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
|
||||
|
||||
</scenario>
|
||||
+21
-2
@@ -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);
|
||||
@@ -44,6 +45,10 @@ test('sbc-outbound tests', async(t) => {
|
||||
await sippUac('uac-pcap-carrier-success.xml');
|
||||
t.pass('successfully completed outbound call to sip trunk');
|
||||
|
||||
/* call to Sip URI with no lcr configured */
|
||||
await sippUac('uac-pcap-sip-routing-success.xml');
|
||||
t.pass('successfully completed outbound call to sip routing trunk');
|
||||
|
||||
/* call to PSTN with no lcr configured */
|
||||
await sippUac('uac-pcap-inbound-carrier-success.xml');
|
||||
t.pass('successfully completed outbound call to sip trunk');
|
||||
@@ -80,6 +85,10 @@ test('sbc-outbound tests', async(t) => {
|
||||
await sippUac('uac-sip-uri-auth-success.xml');
|
||||
t.pass('successfully connected to sip uri that requires auth');
|
||||
|
||||
/* invite to sipUri through proxy */
|
||||
await sippUac('uac-sip-uri-proxy.xml');
|
||||
t.pass('successfully connected to sip uri through proxy');
|
||||
|
||||
// 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-data5.sql`);
|
||||
@@ -87,12 +96,22 @@ 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'});
|
||||
console.log(`${res.total} cdrs: ${JSON.stringify(res)}`);
|
||||
t.ok(res.total === 7, 'wrote 7 cdrs');
|
||||
t.ok(res.total === 9, 'wrote 9 cdrs');
|
||||
|
||||
srf.disconnect();
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user