Compare commits

...
52 Commits
Author SHA1 Message Date
Paulo Tellesandp.souza f352bf885c improve dockerfile to fix snyk security issues (#41)
Co-authored-by: p.souza <p.souza@cognigy.com>
2022-07-07 15:19:14 +02:00
Dave Horton 08a4f5defb update Dockerfile 2022-06-23 09:57:38 -04:00
Dave Horton 87df38110b update to azure 1.22.0 2022-06-11 16:23:08 -04:00
Dave Horton 06e370fa59 update deps 2022-06-11 11:55:21 -04:00
Dave Horton e4ed2cea26 healthcheck improvements (#38) 2022-04-12 15:45:33 -04:00
Dave Horton 0c8967acdb bump version 2022-04-06 08:18:22 -04:00
Dave Horton 1dfeed3ac8 bugfix: mem leak 2022-04-05 19:57:03 -04:00
Dave Horton d10bda2926 update deps 2022-04-01 13:46:16 -04:00
Dave Horton 3938773738 mem leak fix 2022-03-28 09:09:14 -04:00
Dave Horton f881002943 write otel trace_id to call history 2022-03-23 09:26:04 -04:00
Dave Horton 2266b80e73 release drachtio connection on final outbound failure 2022-03-18 14:34:30 -04:00
Snyk bot 4c3d6ddf0c fix: Dockerfile to reduce vulnerabilities (#35)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DEBIAN11-GNUTLS28-2419151
- https://snyk.io/vuln/SNYK-DEBIAN11-OPENSSL-2388380
- https://snyk.io/vuln/SNYK-DEBIAN11-OPENSSL-2426309
- https://snyk.io/vuln/SNYK-DEBIAN11-UTILLINUX-2401081
- https://snyk.io/vuln/SNYK-DEBIAN11-UTILLINUX-2401081
2022-03-18 07:55:10 -04:00
Dave Horton 3d1bcb23f4 bump version 2022-03-08 20:16:19 -05:00
Dave Horton 98ecfa20aa add support for redis auth 2022-03-08 09:30:44 -05:00
Dave Horton 56efe50aec forward incoming REFER to FS (#34) 2022-03-05 15:21:49 -05:00
Dave Horton 891d0ff38b use registered contact as uri when sending to user (#32) 2022-02-23 15:09:18 -05:00
Dave Horton 78d6cb5f22 use registered contact as uri when sending to user (#31) 2022-02-17 21:35:19 -05:00
Dave Horton 23255a71db added pre-commit hook for linting 2022-02-14 13:18:46 -05:00
Dave Horton 4cb34ad784 regression handling 302 redirect 2022-02-14 12:11:32 -05:00
Dave Horton f096a31a95 bump version 2022-02-09 15:43:50 -05:00
Dave Horton f671ef56bb update to latest @jambonz/realtimedb-helpers with support for redis username / password auth 2022-02-09 15:16:03 -05:00
Dave Horton 3a2fcf5976 update deps 2022-02-09 08:24:43 -05:00
Dave Horton dd85ec762d merge support for X-Override-To feature back into main branch 2022-02-01 12:30:34 -05:00
Dave Horton 61ec935ab8 0.7.2 version 2022-01-28 09:13:25 -05:00
Snyk bot cd7c425f7e fix: Dockerfile to reduce vulnerabilities (#30)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-1296898
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-2340908
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-2340908
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-2340922
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-2340922
2022-01-28 08:03:40 -05:00
Dave Horton e9b8cd595a Feature/rtpengine locate by dns (#29)
* k8s: query dns for rtpengine endpoints

* use udp now in k8s

* catch error on rtpengine failure

* specify node version in gh actions
2022-01-23 18:04:15 -05:00
Dave Horton fc7cdae22a Feature/rtpengine locate by dns (#28)
* k8s: query dns for rtpengine endpoints

* use udp now in k8s

* catch error on rtpengine failure
2022-01-23 17:32:51 -05:00
Dave Horton 2ca44c4f5a healthcheck only in k8s 2022-01-22 21:43:18 -05:00
Dave Horton 50ff35c582 update to rtpengine-utils that reconnects ws 2022-01-19 19:25:47 -05:00
Dave Horton a318b07f61 Merge branch 'main' of github.com:jambonz/sbc-outbound into main 2022-01-17 18:40:07 -05:00
Dave Horton be354cde10 avoid adding multiple reset flags to rtpengine params 2022-01-17 18:39:51 -05:00
Snyk bot 594e8983e8 fix: Dockerfile to reduce vulnerabilities (#27)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-1296898
- https://snyk.io/vuln/SNYK-DEBIAN11-GLIBC-1296898
- https://snyk.io/vuln/SNYK-DEBIAN11-GMP-1920938
- https://snyk.io/vuln/SNYK-DEBIAN11-LIBGCRYPT20-1297892
- https://snyk.io/vuln/SNYK-DEBIAN11-PERL-1925976
2022-01-17 07:50:37 -05:00
Dave Horton 13f7d583df use ws rather than tcp for rtpengine connection in K8S 2022-01-12 08:28:59 -05:00
Dave Horton 6085452469 add support for tcp connections to rtpengine (#26) 2022-01-11 15:46:52 -05:00
Dave Horton 64ac043656 add support for using ws to connect to rtpengine 2022-01-10 22:01:41 -05:00
Dave Horton 7e9d48ee5b JAMBONES_NETWORK_CIDR not needed for K8S (#25) 2022-01-09 14:59:44 -05:00
Dave Horton fa7de71650 support for routing based on X-Jambonz-Routing header 2022-01-08 13:01:02 -05:00
Dave Horton 9d228d4805 K8s changes (#24)
* support multiple CIDRs

* use outbound in k8s

* k8s: user service for rtpengine

* bugfix: drachtio connection was dropped after successful connect

* Dockerfile

* k8s pre-stop hook

* make hjook executable

* update deps
2022-01-06 12:37:04 -05:00
Dave Horton 607fb3d127 bugfix: dialed numbers less than 8 digits are allowed since a voip_carrier may handle calls that are not actually routed to the PSTN (e.g. PBX) 2021-12-21 20:18:31 -05:00
Dave Horton 8da9c4ffa1 bump version 2021-12-21 09:46:04 -05:00
Dave Horton 97131be582 added docker publish 2021-12-13 14:17:27 -05:00
Dave Horton 3144cfd9c9 version bump 2021-12-13 09:53:11 -05:00
Dave Horton a1810e577a bugfix: outbound calls where carrier was specified in dial verb was losing auth 2021-12-02 19:20:04 -05:00
Dave Horton bd6c39d9ac Feature/specify trunk on dial (#19)
* add support for outdials specifying carrier to use

* fixes from testing

* add support for outdials specifying carrier to use

* rebase

* fix package lock
2021-11-28 11:07:19 -05:00
Snyk bot 24a974f48d fix: upgrade pino from 6.11.3 to 6.13.3 (#14)
Snyk has created this PR to upgrade pino from 6.11.3 to 6.13.3.

See this package in npm:
https://www.npmjs.com/package/pino

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/282b1881-3e13-4fad-85bd-3b1c662c2134?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-21 10:42:28 -05:00
Snyk bot 1dfad575b9 fix: upgrade debug from 4.3.1 to 4.3.2 (#17)
Snyk has created this PR to upgrade debug from 4.3.1 to 4.3.2.

See this package in npm:
https://www.npmjs.com/package/debug

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/282b1881-3e13-4fad-85bd-3b1c662c2134?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-21 10:42:17 -05:00
Snyk bot dc30075665 fix: upgrade @jambonz/realtimedb-helpers from 0.4.3 to 0.4.8 (#15)
Snyk has created this PR to upgrade @jambonz/realtimedb-helpers from 0.4.3 to 0.4.8.

See this package in npm:
https://www.npmjs.com/package/@jambonz/realtimedb-helpers

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/282b1881-3e13-4fad-85bd-3b1c662c2134?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-20 23:03:53 -05:00
Snyk bot e2bcc16568 fix: upgrade @jambonz/db-helpers from 0.6.12 to 0.6.13 (#16)
Snyk has created this PR to upgrade @jambonz/db-helpers from 0.6.12 to 0.6.13.

See this package in npm:
https://www.npmjs.com/package/@jambonz/db-helpers

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/282b1881-3e13-4fad-85bd-3b1c662c2134?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-20 23:03:28 -05:00
Snyk bot a9ee51d671 fix: upgrade @jambonz/stats-collector from 0.1.5 to 0.1.6 (#18)
Snyk has created this PR to upgrade @jambonz/stats-collector from 0.1.5 to 0.1.6.

See this package in npm:
https://www.npmjs.com/package/@jambonz/stats-collector

See this project in Snyk:
https://app.snyk.io/org/davehorton/project/282b1881-3e13-4fad-85bd-3b1c662c2134?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-11-20 23:03:00 -05:00
Dave Horton 9e45de261c add support for app-initiated call transfer via REFER (#13) 2021-11-20 11:40:44 -05:00
Dave Horton 5eb209354a Dockerfile 2021-11-04 12:57:24 -04:00
Dave Horton 4c3c167fe0 version bump 2021-11-03 13:52:04 -04:00
25 changed files with 2433 additions and 1447 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14.x
- run: npm ci
- run: npm run jslint
- run: npm test
+51
View File
@@ -0,0 +1,51 @@
name: Docker
on:
push:
# Publish `main` as Docker `latest` image.
branches:
- main
# Publish `v1.2.3` tags as releases.
tags:
- v*
env:
IMAGE_NAME: sbc-outbound
jobs:
push:
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v2
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "main" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run jslint
+18 -10
View File
@@ -1,15 +1,23 @@
FROM node:alpine as builder
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune --production
FROM --platform=linux/amd64 node:16.15.1-alpine as base
FROM node:alpine as app
WORKDIR /opt/app
COPY . /opt/app
COPY --from=builder /opt/app/node_modules ./node_modules
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
WORKDIR /opt/app/
FROM base as build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
FROM base
COPY --from=build /opt/app /opt/app/
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "start" ]
CMD [ "node", "app.js" ]
+92 -26
View File
@@ -6,12 +6,12 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
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, 'missing JAMBONES_NETWORK_CIDR env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR env var');
const Srf = require('drachtio-srf');
const srf = new Srf('sbc-outbound');
const CIDRMatcher = require('cidr-matcher');
const matcher = new CIDRMatcher([process.env.JAMBONES_NETWORK_CIDR]);
const {equalsIgnoreOrder, pingMsTeamsGateways, createHealthCheckApp, systemHealth} = require('./lib/utils');
const opts = Object.assign({
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
@@ -32,11 +32,14 @@ const CallSession = require('./lib/call-session');
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = [];
const {
ping,
performLcr,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
lookupAccountCapacitiesBySid,
lookupSipGatewaysByCarrier,
lookupCarrierBySid
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
@@ -44,7 +47,15 @@ const {
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
}, logger);
const {createHash, retrieveHash, incrKey, decrKey, retrieveSet} = require('@jambonz/realtimedb-helpers')({
const {
client: redisClient,
createHash,
retrieveHash,
incrKey,
decrKey,
retrieveSet,
isMemberOfSet
} = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
@@ -59,17 +70,21 @@ srf.locals = {...srf.locals,
queryCdrs,
activeCallIds,
dbHelpers: {
ping,
performLcr,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
lookupAccountCapacitiesBySid,
lookupSipGatewaysByCarrier,
lookupCarrierBySid
},
realtimeDbHelpers: {
createHash,
retrieveHash,
incrKey,
decrKey
decrKey,
isMemberOfSet
}
};
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, {
@@ -78,11 +93,18 @@ const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger
});
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
emitter: stats,
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22225
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22225,
protocol: 'udp'
});
srf.locals.getRtpEngine = getRtpEngine;
if (process.env.DRACHTIO_HOST) {
if (process.env.DRACHTIO_HOST && !process.env.K8S) {
const cidrs = process.env.JAMBONES_NETWORK_CIDR
.split(',')
.map((s) => s.trim());
logger.info({cidrs}, 'internal network CIDRs');
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) => {
logger.info(`connected to drachtio listening on ${hp}`);
@@ -103,6 +125,7 @@ if (process.env.DRACHTIO_HOST) {
});
}
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});
}
if (process.env.NODE_ENV === 'test') {
@@ -117,33 +140,78 @@ srf.invite((req, res) => {
session.connect();
});
/* update call stats periodically */
setInterval(() => {
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:outbound']);
}, 5000);
if (process.env.K8S || process.env.HTTP_PORT) {
const PORT = process.env.HTTP_PORT || 3000;
const healthCheck = require('@jambonz/http-health-check');
const arrayCompare = (a, b) => {
if (a.length !== b.length) return false;
const uniqueValues = new Set([...a, ...b]);
for (const v of uniqueValues) {
const aCount = a.filter((e) => e === v).length;
const bCount = b.filter((e) => e === v).length;
if (aCount !== bCount) return false;
}
return true;
const getCount = () => srf.locals.activeCallIds.size;
createHealthCheckApp(PORT, logger)
.then((app) => {
healthCheck({
app,
logger,
path: '/',
fn: getCount
});
healthCheck({
app,
logger,
path: '/system-health',
fn: systemHealth.bind(null, redisClient, ping, getCount)
});
return;
})
.catch((err) => {
logger.error({err}, 'Error creating health check server');
});
}
if ('test' !== process.env.NODE_ENV) {
/* update call stats periodically */
setInterval(() => {
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:outbound']);
}, 5000);
}
const lookupRtpServiceEndpoints = (lookup, serviceName) => {
logger.debug(`dns lookup for ${serviceName}..`);
lookup(serviceName, {family: 4, all: true}, (err, addresses) => {
if (err) {
logger.error({err}, `Error looking up ${serviceName}`);
return;
}
logger.debug({addresses, rtpServers}, `dns lookup for ${serviceName} returned`);
const addrs = addresses.map((a) => a.address);
if (!equalsIgnoreOrder(addrs, rtpServers)) {
rtpServers.length = 0;
Array.prototype.push.apply(rtpServers, addrs);
logger.info({rtpServers}, 'rtpserver endpoints have been updated');
setRtpEngines(rtpServers.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
}
});
};
/* update rtpengines periodically */
if (process.env.JAMBONES_RTPENGINES) {
if (process.env.K8S_RTPENGINE_SERVICE_NAME) {
/* poll dns for endpoints every so often */
const arr = /^(.*):(\d+)$/.exec(process.env.K8S_RTPENGINE_SERVICE_NAME);
const svc = arr[1];
logger.info(`rtpengine(s) will be found at dns name: ${svc}`);
const {lookup} = require('dns');
lookupRtpServiceEndpoints(lookup, svc);
setInterval(lookupRtpServiceEndpoints.bind(null, lookup, svc), process.env.RTPENGINE_DNS_POLL_INTERVAL || 10000);
}
else if (process.env.JAMBONES_RTPENGINES) {
/* static list of rtpengines */
setRtpEngines([process.env.JAMBONES_RTPENGINES]);
}
else {
/* poll redis periodically for rtpengines that have registered via OPTIONS ping */
const getActiveRtpServers = async() => {
try {
const set = await retrieveSet(setNameRtp);
const newArray = Array.from(set);
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
if (!arrayCompare(newArray, rtpServers)) {
if (!equalsIgnoreOrder(newArray, rtpServers)) {
logger.info({newArray}, 'resetting active rtpengines');
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
rtpServers.length = 0;
@@ -153,14 +221,12 @@ else {
logger.error({err}, 'Error setting new rtpengines');
}
};
setInterval(() => {
getActiveRtpServers();
}, 30000);
getActiveRtpServers();
}
const {pingMsTeamsGateways} = require('./lib/utils');
pingMsTeamsGateways(logger, srf);
module.exports = {srf};
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env node
const bent = require('bent');
const getJSON = bent('json');
const PORT = process.env.HTTP_PORT || 3000;
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
(async function() {
try {
do {
const obj = await getJSON(`http://127.0.0.1:${PORT}/`);
const {calls} = obj;
if (calls === 0) {
console.log('no calls on the system, we can exit');
process.exit(0);
}
else {
console.log(`waiting for ${calls} to exit..`);
}
await sleep(10000);
} while (1);
} catch (err) {
console.error(err, 'Error querying health endpoint');
process.exit(-1);
}
})();
+151 -39
View File
@@ -1,8 +1,7 @@
const Emitter = require('events');
const {makeRtpEngineOpts, makeCallCountKey} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
const {SipError} = require('drachtio-srf');
const {parseUri} = require('drachtio-srf');
const {SipError, stringifyUri, parseUri} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-outbound');
const makeInviteInProgressKey = (callid) => `sbc-out-iip${callid}`;
@@ -44,7 +43,8 @@ const initCdr = (srf, req) => {
attempted_at: Date.now(),
direction: 'outbound',
host: srf.locals.sipAddress,
remote_host: uri.host
remote_host: uri.host,
trace_id: req.get('X-Trace-ID') || '00000000000000000000000000000000'
};
};
@@ -54,7 +54,6 @@ class CallSession extends Emitter {
this.req = req;
this.res = res;
this.srf = req.srf;
this.performLcr = this.srf.locals.dbHelpers.performLcr;
this.logger = logger.child({callId: req.get('Call-ID')});
this.useWss = req.locals.registration && req.locals.registration.protocol === 'wss';
this.stats = this.srf.locals.stats;
@@ -64,6 +63,11 @@ class CallSession extends Emitter {
this.incrKey = req.srf.locals.realtimeDbHelpers.incrKey;
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
this.callCountKey = makeCallCountKey(req.locals.account_sid);
const {performLcr, lookupCarrierBySid, lookupSipGatewaysByCarrier} = this.srf.locals.dbHelpers;
this.performLcr = performLcr;
this.lookupCarrierBySid = lookupCarrierBySid;
this.lookupSipGatewaysByCarrier = lookupSipGatewaysByCarrier;
}
get account_sid() {
@@ -121,14 +125,19 @@ class CallSession extends Emitter {
};
if (this.req.locals.registration) {
debug(`sending call to user ${JSON.stringify(this.req.locals.registration)}`);
debug(`sending call to registered user ${JSON.stringify(this.req.locals.registration)}`);
const contact = this.req.locals.registration.contact;
if (contact.includes('transport=ws')) {
uris = [contact];
let destUri = contact;
if (this.req.has('X-Override-To')) {
const dest = this.req.get('X-Override-To');
const uri = parseUri(contact);
uri.user = dest;
destUri = stringifyUri(uri);
this.logger.info(`overriding destination user with ${dest}, so final uri is ${destUri}`);
}
else {
uris = [destUri];
if (!contact.includes('transport=ws')) {
proxy = this.req.locals.registration.proxy;
uris = [this.req.uri];
}
}
else if (this.req.locals.target === 'forward') {
@@ -149,27 +158,64 @@ class CallSession extends Emitter {
else {
debug('calling lcr');
try {
/**
* We normalize the called number by removing a leading + before sending it to LCR..
* but LCR will return us an array of sip uris, with leading + for carriers that require it
*/
const routableNumber = this.req.calledNumber.startsWith('+') ?
this.req.calledNumber.slice(1) :
this.req.calledNumber;
const gateways = await this.performLcr(routableNumber, this.account_sid);
if (!gateways || gateways.length === 0) throw new Error('no routes found');
debug(`got gateways: ${JSON.stringify(gateways)}`);
gateways.forEach((gw) => mapGateways.set(gw.uri, {
name: gw.name,
auth: gw.auth,
diversion: gw.diversion,
hostport: gw.hostport
}));
uris = gateways.map((gw) => gw.uri);
/* was a specific carrier requested */
const voip_carrier_sid = this.req.get('X-Requested-Carrier-Sid');
if (voip_carrier_sid) {
const vc = await this.lookupCarrierBySid(voip_carrier_sid);
const gateways = await this.lookupSipGatewaysByCarrier(voip_carrier_sid);
const gws = (gateways || [])
.filter((gw) => gw.outbound);
if (gws.length) {
uris = [];
gws.forEach((o) => {
const prefix = vc.tech_prefix;
const hostport = !o.port || 5060 === o.port ? o.ipv4 : `${o.ipv4}:${o.port}`;
const prependPlus = vc.e164_leading_plus && !this.req.calledNumber.startsWith('0');
const u = `sip:${prefix ? prefix : ''}${prependPlus ? '+' : ''}${this.req.calledNumber}@${hostport}`;
const obj = {
name: vc.name,
diversion: vc.diversion,
hostport
};
if (vc.register_username && vc.register_password) {
obj.auth = {
username: vc.register_username,
password: vc.register_password
};
}
mapGateways.set(u, obj);
uris.push(u);
});
this.logger.debug({uris, voip_carrier_sid}, 'selected outbound gateways for requested carrier');
}
else {
this.logger.info({voip_carrier_sid}, 'no outbound gateways found for requested carrier');
}
}
if (mapGateways.size === 0) {
/**
* We normalize the called number by removing a leading + before sending it to LCR..
* but LCR will return us an array of sip uris, with leading + for carriers that require it
*/
const routableNumber = this.req.calledNumber.startsWith('+') ?
this.req.calledNumber.slice(1) :
this.req.calledNumber;
const gateways = await this.performLcr(routableNumber, this.account_sid);
if (!gateways || gateways.length === 0) throw new Error('no routes found');
debug(`got gateways: ${JSON.stringify(gateways)}`);
gateways.forEach((gw) => mapGateways.set(gw.uri, {
name: gw.name,
auth: gw.auth,
diversion: gw.diversion,
hostport: gw.hostport
}));
uris = gateways.map((gw) => gw.uri);
}
} catch (err) {
debug(err);
this.logger.error(err, 'Error performing lcr');
return this.res.send(488);
this.res.send(488);
return this.srf.endSession(this.req);
}
debug(`sending call to PSTN ${uris}`);
}
@@ -237,9 +283,24 @@ class CallSession extends Emitter {
const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, {
proxy,
passFailure,
proxyRequestHeaders: ['all', '-X-MS-Teams-FQDN', '-X-MS-Teams-Tenant-FQDN', 'X-CID', '-Allow',
'-Session-Expires', 'Min-SE'],
proxyResponseHeaders: ['all', '-Allow', '-Session-Expires'],
proxyRequestHeaders: [
'all',
'-X-MS-Teams-FQDN',
'-X-MS-Teams-Tenant-FQDN',
'-X-Trace-ID',
'X-CID',
'-Allow',
'-Session-Expires',
'-X-Requested-Carrier-Sid',
'-X-Jambonz-Routing',
'-X-Jambonz-FS-UUID',
'Min-SE'
],
proxyResponseHeaders: [
'all',
'-Allow',
'-Session-Expires'
],
headers: hdrs,
responseHeaders,
auth: gw ? gw.auth : undefined,
@@ -297,6 +358,7 @@ class CallSession extends Emitter {
// successfully connected
this.logger.info(`call connected to ${uri}`);
this.connectedUri = uri;
debug('call connected');
this.emit('connected', uri);
@@ -317,7 +379,9 @@ class CallSession extends Emitter {
debug(`got final outdial error: ${err}`);
if (!passFailure) this.res.send(status);
this.emit('failed');
this.rtpEngineResource.destroy();
this.rtpEngineResource.destroy()
.catch((err) => this.logger.info({err}, 'Error destroying rtpe after failure'));
this.srf.endSession(this.req);
const tags = ['accepted:no', `sipStatus:${status}`];
this.stats.increment('sbc.originations', tags);
@@ -325,7 +389,7 @@ class CallSession extends Emitter {
this.writeCdrs({...this.req.locals.cdr,
terminated_at: Date.now(),
termination_reason: 487 === status ? 'caller abandoned' : 'failed',
sip_status: status,
sip_status: status
}).catch((err) => this.logger.error({err}, 'Error writing cdr for call failure'));
}
}
@@ -335,8 +399,9 @@ class CallSession extends Emitter {
}
}
} catch (err) {
if ('abandonded' !== err.message) this.logger.error(err, `Error setting up outbonund call to: ${uris}`);
if ('abandonded' !== err.message) this.logger.error(err, `Error setting up outbound call to: ${uris}`);
this.emit('failed');
this.srf.endSession(this.req);
this.rtpEngineResource.destroy();
}
}
@@ -356,12 +421,14 @@ class CallSession extends Emitter {
this.uas = uas;
this.uac = uac;
[uas, uac].forEach((dlg) => {
dlg.on('destroy', () => {
this.logger.info('call ended');
dlg.on('destroy', async() => {
const other = dlg.other;
this.rtpEngineResource.destroy();
this.activeCallIds.delete(this.req.get('Call-ID'));
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uac.tag);
dlg.other.destroy();
try {
await other.destroy();
} catch (err) {}
this.decrKey(this.callCountKey)
.then((count) => {
@@ -382,6 +449,15 @@ class CallSession extends Emitter {
duration: Math.floor((now - callStart) / 1000)
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
}
/* de-link the 2 Dialogs for GC */
dlg.removeAllListeners();
other.removeAllListeners();
dlg.other = null;
other.other = null;
this.logger.info(`call ended with normal termination, there are ${this.activeCallIds.size} active`);
this.srf.endSession(this.req);
});
});
@@ -392,6 +468,7 @@ class CallSession extends Emitter {
uac.on('modify', this._onReinvite.bind(this, uac));
uas.on('refer', this._onFeatureServerTransfer.bind(this, uas));
uac.on('refer', this._onRefer.bind(this, uac));
uas.on('info', this._onInfo.bind(this, uas));
uac.on('info', this._onInfo.bind(this, uac));
@@ -400,6 +477,23 @@ class CallSession extends Emitter {
forwardInDialogRequests(uac, ['notify', 'options', 'message']);
}
async _onRefer(dlg, req, res) {
/* REFER coming in from a sip device, forward to feature server */
try {
const response = await dlg.other.request({
method: 'REFER',
headers: {
'Refer-To': req.get('Refer-To'),
'Referred-By': req.get('Referred-By'),
'User-Agent': req.get('User-Agent')
}
});
res.send(response.status, response.reason);
} catch (err) {
this.logger.error({err}, 'CallSession:_onRefer: error handling incoming REFER');
}
}
async _onDTMF(dlg, payload) {
this.logger.info({payload}, '_onDTMF');
try {
@@ -444,7 +538,8 @@ Duration=${payload.duration} `
direction,
sdp: req.body,
};
if (reason) opts.flags.push('reset');
if (reason && opts.flags && !opts.flags.includes('reset')) opts.flags.push('reset');
let response = await this.offer(opts);
if ('ok' !== response.result) {
@@ -460,6 +555,7 @@ Duration=${payload.duration} `
}
else {
sdp = await dlg.other.modify(response.sdp);
this.logger.info({sdp}, 'CallSession:_onReinvite: got sdp from 200 OK to invite we sent');
}
opts = {
...this.rtpEngineOpts.common,
@@ -473,6 +569,7 @@ Duration=${payload.duration} `
res.send(488);
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
}
this.logger.info({sdp: response.sdp}, 'CallSession:_onReinvite: sending back upstream');
res.send(200, {body: response.sdp});
} catch (err) {
this.logger.error(err, 'Error handling reinvite');
@@ -523,8 +620,22 @@ Duration=${payload.duration} `
this.logger.info({uri, referTo}, 'received REFER from feature server');
const arr = /context-(.*)/.exec(uri.user);
if (!arr) {
this.logger.info(`invalid Refer-To header: ${referTo.uri}`);
return res.send(501);
/* call transfer requested */
const referredBy = req.getParsedHeader('Referred-By');
if (!referredBy) return res.send(400);
const u = parseUri(referredBy.uri);
const farEnd = parseUri(this.connectedUri);
uri.host = farEnd.host;
uri.port = farEnd.port;
const response = await this.uac.request({
method: 'REFER',
headers: {
'Refer-To': stringifyUri(uri),
'Referred-By': stringifyUri(u)
}
});
return res.send(response.status);
}
res.send(202);
@@ -544,6 +655,7 @@ Duration=${payload.duration} `
this.rtpEngineResource.destroy();
this.activeCallIds.delete(this.req.get('Call-ID'));
this.uas.other.destroy();
this.srf.endSession(this.req);
});
// modify rtpengine to stream to new feature server
+58 -36
View File
@@ -2,15 +2,10 @@ const debug = require('debug')('jambonz:sbc-outbound');
const parseUri = require('drachtio-srf').parseUri;
const Registrar = require('@jambonz/mw-registrar');
const {selectHostPort, makeCallCountKey} = require('./utils');
const isLocalUri = (host, req) => {
debug({hostport: req.server.hostport}, `is ${host} local?`);
return req.server.hostport.includes(host);
};
const FS_UUID_SET_NAME = 'fsUUIDs';
module.exports = (srf, logger, opts) => {
const {incrKey, decrKey} = srf.locals.realtimeDbHelpers;
const {incrKey, decrKey, isMemberOfSet} = srf.locals.realtimeDbHelpers;
const {stats} = srf.locals;
const registrar = new Registrar(opts);
const {
@@ -22,17 +17,53 @@ module.exports = (srf, logger, opts) => {
req.locals = req.locals || {};
const callId = req.get('Call-ID');
req.locals.account_sid = req.get('X-Account-Sid');
req.locals.logger = logger.child({callId, account_sid: req.locals.account_sid});
const traceId = req.locals.trace_id = req.get('X-Trace-ID');
req.locals.logger = logger.child({
callId,
traceId,
account_sid:
req.locals.account_sid});
if (!req.locals.account_sid) {
logger.info('missing X-Account-Sid on outbound call');
return res.send(403, {
res.send(403, {
headers: {
'X-Reason': 'missing X-Account-Sid'
}
});
return req.srf.endSession(req);
}
/* must come from a valid FS */
if (!req.has('X-Jambonz-Routing')) {
logger.info({msg: req.msg}, 'missing X-Jambonz-Routing header');
res.send(403, {
headers: {
'X-Reason': 'missing required jambonz headers'
}
});
return req.srf.endSession(req);
}
if (process.env.K8S) {
/* for K8S we do not use JAMBONES_CIDR so we must validate the sender by uuid FS creates */
const fsUUID = req.get('X-Jambonz-FS-UUID');
try {
const exists = await isMemberOfSet(FS_UUID_SET_NAME, fsUUID);
if (!exists || !fsUUID) {
res.send(403, {
headers: {
'X-Reason': `missing or invalid FS-UUID ${fsUUID}`
}
});
return req.srf.endSession(req);
}
} catch (err) {
res.send(500);
return req.srf.endSession(req);
}
}
stats.increment('sbc.invites', ['direction:outbound']);
req.on('cancel', () => {
@@ -47,7 +78,8 @@ module.exports = (srf, logger, opts) => {
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
} catch (err) {
req.locals.logger.error({err}, `Error looking up account sid ${req.locals.account_sid}`);
return res.send(500);
res.send(500);
return req.srf.endSession(req);
}
next();
};
@@ -106,6 +138,7 @@ module.exports = (srf, logger, opts) => {
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
res.send(503, 'Maximum Calls In Progress');
return req.srf.endSession(req);
}
next();
} catch (err) {
@@ -119,6 +152,7 @@ module.exports = (srf, logger, opts) => {
const {lookupAccountBySipRealm} = req.srf.locals.dbHelpers;
logger.info(`received outbound INVITE to ${req.uri} from server at ${req.server.hostport}`);
const uri = parseUri(req.uri);
const desiredRouting = req.get('X-Jambonz-Routing');
if (!uri || !uri.user || !uri.host) {
logger.info({uri: req.uri}, 'invalid request-uri on outbound call, rejecting');
@@ -127,23 +161,17 @@ module.exports = (srf, logger, opts) => {
'X-Reason': 'invalid request-uri'
}
});
return;
return req.srf.endSession(req);
}
const aor = `${uri.user}@${uri.host}`;
let reg;
const dotDecimalHost = /^[0-9\.]+$/.test(uri.host);
debug(`received outbound INVITE to ${req.calledNumber} from server at ${req.server.hostport}`);
if (req.has('X-MS-Teams-FQDN') && req.has('X-MS-Teams-Tenant-FQDN')) {
if ('teams' === desiredRouting) {
logger.debug('This is a call to ms teams');
req.locals.target = 'teams';
return next();
}
else if (!dotDecimalHost) {
// uri host is not a dot-decimal address, so try to look up user
logger.debug(`searching for registered user ${aor}`);
reg = await registrar.query(aor);
else if ('user' === desiredRouting) {
const aor = `${uri.user}@${uri.host}`;
const reg = await registrar.query(aor);
if (reg) {
// user is registered..find out which sbc is handling it
// us => we can put the call through
@@ -158,34 +186,28 @@ module.exports = (srf, logger, opts) => {
}
req.locals.registration = reg;
req.locals.target = 'user';
return next();
}
else {
// if the sip domain is one of ours return 404
const account = await lookupAccountBySipRealm(uri.host);
if (account) {
logger.info({host: uri.host, account}, `returning 404 to unregistered user in valid domain: ${req.uri}`);
res.send(404);
return;
}
else {
logger.info({host: uri.host, account}, `returning 404 to user in invalid domain: ${req.uri}`);
}
res.send(404);
return req.srf.endSession(req);
}
}
if (!dotDecimalHost || !isLocalUri(uri.host, req)) {
else if ('sip' === desiredRouting) {
// call that needs to be forwarded to a sip endpoint
logger.info(`forwarding call to sip endpoint ${req.uri}`);
req.locals.target = 'forward';
return next();
}
// if the called number is digits only (after possible leading plus sign) and long enough, do lcr
if (!/^\d+$/.test(req.calledNumber.slice(1)) || req.calledNumber.length < 8) {
debug(`unable to route call to ${aor}; no registered user found`);
logger.info(`unable to route call to ${aor}; no registered user found`);
return res.send(404);
else if ('phone' === desiredRouting) {
debug('sending call to LCR');
req.locals.target = 'lcr';
}
debug('sending call to LCR');
req.locals.target = 'lcr';
next();
};
+35 -1
View File
@@ -73,9 +73,43 @@ const pingMsTeamsGateways = (logger, srf) => {
const makeCallCountKey = (sid) => `${sid}:outcalls`;
const equalsIgnoreOrder = (a, b) => {
if (a.length !== b.length) return false;
const uniqueValues = new Set([...a, ...b]);
for (const v of uniqueValues) {
const aCount = a.filter((e) => e === v).length;
const bCount = b.filter((e) => e === v).length;
if (aCount !== bCount) return false;
}
return true;
};
const systemHealth = async(redisClient, ping, getCount) => {
await Promise.all([redisClient.ping(), ping()]);
return getCount();
};
const createHealthCheckApp = (port, logger) => {
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
return new Promise((resolve) => {
app.listen(port, () => {
logger.info(`Health check server started at http://localhost:${port}`);
resolve(app);
});
});
};
module.exports = {
makeRtpEngineOpts,
selectHostPort,
pingMsTeamsGateways,
makeCallCountKey
makeCallCountKey,
equalsIgnoreOrder,
systemHealth,
createHealthCheckApp
};
+1950 -1315
View File
File diff suppressed because it is too large Load Diff
+18 -15
View File
@@ -1,9 +1,9 @@
{
"name": "sbc-outbound",
"version": "0.6.7-rc3",
"version": "v0.7.5",
"main": "app.js",
"engines": {
"node": ">= 8.10.0"
"node": ">= 12.0.0"
},
"keywords": [
"sip",
@@ -22,27 +22,30 @@
"description": "jambonz session border controller application for outbound calls",
"scripts": {
"start": "node app",
"test": "NODE_ENV=test JAMBONZ_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/ ",
"test": "NODE_ENV=test HTTP_PORT=3050 JAMBONZ_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"
},
"dependencies": {
"@jambonz/db-helpers": "^0.6.12",
"@jambonz/mw-registrar": "0.2.1",
"@jambonz/realtimedb-helpers": "^0.4.3",
"@jambonz/rtpengine-utils": "^0.1.17",
"@jambonz/stats-collector": "^0.1.5",
"@jambonz/time-series": "^0.1.5",
"@jambonz/db-helpers": "^0.6.18",
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "0.2.2",
"@jambonz/realtimedb-helpers": "^0.4.29",
"@jambonz/rtpengine-utils": "^0.3.1",
"@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.1.9",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.1",
"debug": "^4.3.4",
"drachtio-fn-b2b-sugar": "^0.0.12",
"drachtio-srf": "^4.4.55",
"pino": "^6.11.2"
"drachtio-srf": "^4.5.0",
"express": "^4.18.1",
"pino": "^7.11.0"
},
"devDependencies": {
"eslint": "^7.18.0",
"eslint-plugin-promise": "^4.2.1",
"bent": "^7.3.12",
"eslint": "^7.32.0",
"eslint-plugin-promise": "^5.2.0",
"nyc": "^15.1.0",
"tape": "^5.2.2"
"tape": "^5.5.3"
}
}
+2 -1
View File
@@ -10,6 +10,7 @@ networks:
services:
mysql:
image: mysql:5.7
platform: linux/x86_64
ports:
- "3306:3306"
environment:
@@ -118,7 +119,7 @@ services:
ipv4_address: 172.39.0.24
influxdb:
image: influxdb:1.8-alpine
image: influxdb:1.8
ports:
- "8086:8086"
networks:
+4
View File
@@ -5,3 +5,7 @@ DRACHTIO_SECRET=cymru
JAMBONES_REDIS_HOST=172.39.0.11
JAMBONES_REDIS_PORT=6379
JAMBONES_LOGLEVEL=info
JAMBONES_MYSQL_HOST=172.39.0.2
JAMBONES_MYSQL_USER=jambones_test
JAMBONES_MYSQL_PASSWORD=jambones_test
JAMBONES_MYSQL_DATABASE=jambones_test
+1 -1
View File
@@ -36,6 +36,7 @@
Subject: uac-pcap-carrier-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: phone
Content-Type: application/sdp
Content-Length: [len]
@@ -103,4 +104,3 @@
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>
@@ -34,6 +34,7 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
@@ -34,6 +34,7 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
@@ -34,6 +34,7 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
@@ -16,6 +16,7 @@
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]
@@ -17,6 +17,7 @@
Subject: uac-pcap-carrier-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: phone
Content-Type: application/sdp
Content-Length: [len]
@@ -16,6 +16,7 @@
Max-Forwards: 70
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: phone
Subject: uac-pcap-carrier-success
Content-Type: application/sdp
Content-Length: [len]
+1
View File
@@ -37,6 +37,7 @@
Subject: uac-pcap-device-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: ff649e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
@@ -34,6 +34,7 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-device-success
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
@@ -35,6 +35,7 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-device-success
X-Jambonz-Routing: user
Content-Type: application/sdp
Content-Length: [len]
+3 -1
View File
@@ -37,6 +37,7 @@
Subject: uac-sip-uri-auth-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: sip
Content-Type: application/sdp
Content-Length: [len]
@@ -90,6 +91,7 @@
Subject: uac-sip-uri-auth-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
X-Jambonz-Routing: sip
Content-Type: application/sdp
Content-Length: [len]
@@ -126,7 +128,7 @@
To: <sip:sip:john@jambonz.org>[peer_tag_param]
[last_Call-ID:]
CSeq: 2 ACK
Subject: uac-sip-uri-auth-success
Subject:
Content-Length: 0
]]>
+7 -1
View File
@@ -2,7 +2,8 @@ const test = require('tape');
const { output, sippUac } = require('./sipp')('test_sbc-outbound');
const {execSync} = require('child_process');
const debug = require('debug')('jambonz:sbc-outbound');
const consoleLogger = {error: console.error, info: console.log, debug: console.log};
const bent = require('bent');
const getJSON = bent('json');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -29,6 +30,11 @@ test('sbc-outbound tests', async(t) => {
try {
await connect(srf);
let obj = await getJSON('http://127.0.0.1:3050/');
t.ok(obj.calls === 0, 'HTTP GET / works (current call count)')
obj = await getJSON('http://127.0.0.1:3050/system-health');
t.ok(obj.calls === 0, 'HTTP GET /system-health works (health check)')
/* call to unregistered user */
debug('successfully connected to drachtio server');
await sippUac('uac-pcap-device-404.xml');