Compare commits

..
17 Commits
Author SHA1 Message Date
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
Dave Horton a018b03310 bump version 2021-10-21 13:08:55 -04:00
Dave Horton a61740556d bump version 2021-10-21 13:01:09 -04:00
Dave Horton 07f3a55ec3 Feature/minimal media anchoring (#12)
* feature: release media from freeswitch

* handle mute/unmute

* linting

* update dep

* support for relaying dtmf via SIP INFO to FS

* fix ci test

* handle outdial error
2021-10-21 11:59:54 -04:00
7 changed files with 3271 additions and 3463 deletions
+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
+3 -8
View File
@@ -1,15 +1,10 @@
FROM node:alpine as builder
FROM node:16
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune --production
FROM node:alpine as app
WORKDIR /opt/app
RUN npm prune
COPY . /opt/app
COPY --from=builder /opt/app/node_modules ./node_modules
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
CMD [ "npm", "start" ]
CMD [ "npm", "start" ]
+6 -2
View File
@@ -36,7 +36,9 @@ const {
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
lookupAccountCapacitiesBySid,
lookupSipGatewaysByCarrier,
lookupCarrierBySid
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
@@ -63,7 +65,9 @@ srf.locals = {...srf.locals,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
lookupAccountCapacitiesBySid,
lookupSipGatewaysByCarrier,
lookupCarrierBySid
},
realtimeDbHelpers: {
createHash,
+79 -24
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}`;
@@ -54,7 +53,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 +62,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() {
@@ -149,23 +152,59 @@ 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');
@@ -238,7 +277,7 @@ class CallSession extends Emitter {
proxy,
passFailure,
proxyRequestHeaders: ['all', '-X-MS-Teams-FQDN', '-X-MS-Teams-Tenant-FQDN', 'X-CID', '-Allow',
'-Session-Expires', 'Min-SE'],
'-Session-Expires', '-X-Requested-Carrier-Sid', 'Min-SE'],
proxyResponseHeaders: ['all', '-Allow', '-Session-Expires'],
headers: hdrs,
responseHeaders,
@@ -265,6 +304,7 @@ class CallSession extends Emitter {
}
}, {
cbRequest: async(err, inv) => {
if (err) return this.logger.info({err}, 'CallSession error sending INVITE');
let trunk = gw ? gw.name : null;
if (!trunk) {
if (teams) trunk = 'Microsoft Teams';
@@ -296,6 +336,7 @@ class CallSession extends Emitter {
// successfully connected
this.logger.info(`call connected to ${uri}`);
this.connectedUri = uri;
debug('call connected');
this.emit('connected', uri);
@@ -309,7 +350,7 @@ class CallSession extends Emitter {
!(err instanceof SipError) || // unexpected error
err.status === 487) { // caller hung up
const abandoned = err.message.includes('rtpengine failed: Unknown call-id');
const abandoned = err.message && err.message.includes('rtpengine failed: Unknown call-id');
const status = err.status || (abandoned ? 487 : 500);
if (err instanceof SipError) this.logger.info(`final call failure ${status}`);
else if (!abandoned) this.logger.error(err, 'unexpected call failure');
@@ -522,8 +563,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);
+2 -2
View File
@@ -177,8 +177,8 @@ module.exports = (srf, logger, opts) => {
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) {
// if the called number is digits only (after possible leading plus sign), do lcr
if (!/^\d+$/.test(req.calledNumber.slice(1))) {
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);
+3118 -3415
View File
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-outbound",
"version": "0.4.6",
"version": "v0.7.1",
"main": "app.js",
"engines": {
"node": ">= 8.10.0"
@@ -27,22 +27,22 @@
"jslint": "eslint app.js lib"
},
"dependencies": {
"@jambonz/db-helpers": "^0.6.12",
"@jambonz/db-helpers": "^0.6.14",
"@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/realtimedb-helpers": "^0.4.9",
"@jambonz/rtpengine-utils": "^0.1.21",
"@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.1.6",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.1",
"debug": "^4.3.3",
"drachtio-fn-b2b-sugar": "^0.0.12",
"drachtio-srf": "^4.4.55",
"pino": "^6.11.2"
"drachtio-srf": "^4.4.59",
"pino": "^7.4.1"
},
"devDependencies": {
"eslint": "^7.18.0",
"eslint-plugin-promise": "^4.2.1",
"eslint": "^7.32.0",
"eslint-plugin-promise": "^5.1.1",
"nyc": "^15.1.0",
"tape": "^5.2.2"
"tape": "^5.3.2"
}
}