Compare commits

...
41 Commits
Author SHA1 Message Date
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
Dave Horton 2ea516be8a bugfix: reject invalid request-uri with 400 Bad Request 2021-10-06 19:12:34 -04:00
Dave Horton d9d2fa9918 update a dep 2021-08-12 09:09:31 -04:00
Dave Horton 16573632fc use single codec in response to freeswitch, otherwise it will get confused 2021-08-12 08:51:07 -04:00
Dave Horton d0f1f16d9a bugfix: write sbc.terminations stat 2021-08-08 10:43:29 -07:00
Dave Horton 98886cfdbf dont check limits on self-hosted deployments 2021-08-04 10:47:46 -04:00
Dave Horton 2ec513b1b0 LICENSE 2021-07-21 12:40:13 -04:00
Dave Horton c3d1e1d605 major merge of features from com version (#11)
major merge of features from the hosted branch that was created temporarily during the initial launch of jambonz.org
2021-06-17 16:51:31 -04:00
Dave Horton 574cdcb216 bugfix: re-invite handling from UAC side now handled properly 2021-04-27 07:36:06 -04:00
Dave Horton 3e01cdeced send 302 to move call to correct SBC, do not send on Session-Expires from response 2021-04-22 14:40:52 -04:00
Dave Horton 05db83a0b3 bugfix prev checkin 2021-04-06 10:21:14 -04:00
Dave Horton 5b801bea84 add support for X-Override-To 2021-04-06 07:42:25 -04:00
Dave Horton e8840c20a0 update deps 2021-01-17 09:55:59 -05:00
Dave Horton 793346fac4 update to latest db-helpers 2021-01-10 12:32:56 -05:00
Dave Horton fd83adadd7 Merge pull request #9 from radicaldrew/master
Updated Dockerfile
2020-12-30 08:59:49 -05:00
Andrew 8a7cc9f3e8 Updated Dockerfile
created multistage build and tested in compose
2020-12-30 15:59:11 +02:00
Dave Horton bc76fc6495 do not pass on X-Account-Sid header to outside world 2020-12-16 13:45:52 -05:00
Dave Horton 0b115a803f ci cleanup 2020-12-14 14:04:31 -05:00
Dave Horton 25a95a5deb ci: fix for docker tty issue 2020-12-14 13:53:32 -05:00
Dave Horton fc5c17848f ci: typo in docker output 2020-12-14 13:47:41 -05:00
Dave Horton 3d2178897c ci: healthcheck for mysqldb 2020-12-14 13:36:29 -05:00
Dave Horton fdf334ec61 ci: debug 2020-12-14 10:27:26 -05:00
Dave Horton da3193f45b ci: more debug 2020-12-14 10:10:52 -05:00
Dave Horton f3564c57bb ci: give mysql more time to come up before starting tests 2020-12-14 10:05:12 -05:00
Dave Horton f580eea102 typo 2020-12-14 09:59:41 -05:00
Dave Horton 5e690b081a pull needed docker image for ci test 2020-12-14 09:57:30 -05:00
Dave Horton a7f5dc4f35 actions 2020-12-14 09:48:44 -05:00
Dave Horton d842dd99b8 more gh actions 2020-12-14 09:43:35 -05:00
Dave Horton 7d0930f6b2 remove mysql service from github actions 2020-12-14 09:41:34 -05:00
Dave Horton 30161b1da0 migrate to github Actions from travis 2020-12-14 09:36:37 -05:00
Dave Horton 2b94a344b1 for ci test, connect to mysql using tcp socket rather than unix socket 2020-12-14 08:41:24 -05:00
Dave Horton 1da347c479 Create npm-publish.yml 2020-12-14 08:34:32 -05:00
Dave Horton 03d6591f8a changes for outbound LCR calls to handle challenges 2020-12-13 16:06:47 -05:00
Dave Horton 180b33f803 update deps 2020-12-13 14:25:13 -05:00
Dave Horton 4e5fbbd650 bugfix #8: maintain Call-ID when sending follow-up INVITE with credentials to endpoint that challenged with 401/407 2020-12-13 13:41:47 -05:00
Dave Horton 1026bcb9cd update deps 2020-11-29 14:55:38 -05:00
Dave Horton 4956e13b9e bugfix: send INVITE to sip device not to contact from register but to source address:port (NAT) 2020-11-29 14:52:13 -05:00
Dave Horton f0cbd6aef5 further fix for proxying outbound call to other SBC 2020-11-29 14:51:16 -05:00
37 changed files with 7288 additions and 1205 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
"jsx": false,
"modules": false
},
"ecmaVersion": 2017
"ecmaVersion": 2018
},
"plugins": ["promise"],
"rules": {
+19
View File
@@ -0,0 +1,19 @@
name: CI
on:
push:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
- run: npm run jslint
- run: npm test
-10
View File
@@ -1,10 +0,0 @@
sudo: required
language: node_js
node_js:
- "lts/*"
services:
- docker
- mysql
script:
- npm test
+9 -7
View File
@@ -1,13 +1,15 @@
FROM node:lts-alpine
FROM node:alpine as builder
WORKDIR /opt/app/
COPY package.json ./
RUN npm install
RUN npm prune --production
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
FROM node:alpine as app
WORKDIR /opt/app
COPY . /opt/app
COPY --from=builder /opt/app/node_modules ./node_modules
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
COPY package.json /usr/src/app/
RUN npm install
COPY . /usr/src/app
CMD [ "npm", "start" ]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 jambonz
Copyright (c) 2021 Drachtio Communications Services, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+63 -67
View File
@@ -1,80 +1,76 @@
# sbc-outbound [![Build Status](https://secure.travis-ci.org/jambonz/sbc-outbound.png)](http://travis-ci.org/jambonz/sbc-outbound)
# sbc-outbound ![Build Status](https://github.com/jambonz/sbc-outbound/workflows/CI/badge.svg)
This application provides a part of the SBC (Session Border Controller) functionality of jambonz. It handles outbound INVITE requests from the cpaas application server that is going to carrier sip trunks or registered sip users/devices, including webrtc applications.
## Configuration
Configuration is provided via the [npmjs config](https://www.npmjs.com/package/config) package. Much of the configuration is standard "where is drachtio?, where is rtpengine?, where is redis?" type of thing. This application also needs information about the sip trunk to send PSTN calls out on. In the initial release, only a single outbound SIP trunk is supported.
Configuration is provided via environment variables:
The following elements make up the configuration for the application:
##### outbound sip trunk
```
"trunks": {
"outbound": {
"name": "carrier1",
"host": "sip:172.39.0.20"
}
},
```
The sip uri specifies the sip trunk uri to send PSTN calls to. All outbound PSTN calls from the cpaas application server are expected to be in E.164 format with a leading plus (+) sign. Outbound sip uris that do not begin with a '+' are assumed to be calls to registered devices/users.
| variable | meaning | required?|
|----------|----------|---------|
|DRACHTIO_HOST| ip address of drachtio server (typically '127.0.0.1')|yes|
|DRACHTIO_PORT| listening port of drachtio server for control connections (typically 9022)|yes|
|DRACHTIO_SECRET| shared secret|yes|
|ENABLE_METRICS| if 1, metrics will be generated|no|
|JAMBONES_LOGLEVEL| log level for application, 'info' or 'debug'|no|
|JAMBONES_MYSQL_HOST| mysql host|yes|
|JAMBONES_MYSQL_USER| mysql username|yes|
|JAMBONES_MYSQL_PASSWORD| mysql password|yes|
|JAMBONES_MYSQL_DATABASE| mysql data|yes|
|JAMBONES_MYSQL_CONNECTION_LIMIT| mysql connection limit |no|
|JAMBONES_NETWORK_CIDR| CIDR of private network that feature server is running in (e.g. '172.31.0.0/16')|yes|
|JAMBONES_REDIS_HOST| redis host|yes|
|JAMBONES_REDIS_PORT|redis port|yes|
|JAMBONES_RTPENGINES| commans-separated list of ip:ng-port for rtpengines (e.g. '172.31.32.10:22222')|yes|
|JAMBONES_SBCS| list of IP addresses (on the internal network) of SBCs, comma-separated|yes|
|STATS_HOST| ip address of metrics host (usually '127.0.0.1' since telegraf is installed locally|no|
|STATS_PORT| listening port for metrics host|no|
|STATS_PROTOCOL| 'tcp' or 'udp'|no|
|STATS_TELEGRAF| if 1, metrics will be generated in telegraf format|no|
##### drachtio server location
### running under pm2
Typically, this application runs under [pm2](https://pm2.io) using an [ecosystem.config.js](https://pm2.keymetrics.io/docs/usage/application-declaration/) file similar to this:
```js
module.exports = {
apps : [
{
name: 'sbc-outbound',
cwd: '/home/admin/apps/sbc-outbound',
script: 'app.js',
instance_var: 'INSTANCE_ID',
out_file: '/home/admin/.pm2/logs/jambonz-sbc-outbound.log',
err_file: '/home/admin/.pm2/logs/jambonz-sbc-outbound.log',
exec_mode: 'fork',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G', env: {
NODE_ENV: 'production',
JAMBONES_LOGLEVEL: 'info',
DRACHTIO_HOST: '127.0.0.1',
DRACHTIO_PORT: 9022,
DRACHTIO_SECRET: 'cymru',
JAMBONES_RTPENGINES: '172.31.32.10:22222',
JAMBONES_MYSQL_HOST: 'aurora-cluster-jambonz.cluster-xxxxxxxxxxxxx.us-west-1.rds.amazonaws.com',
JAMBONES_MYSQL_USER: 'admin',
JAMBONES_MYSQL_PASSWORD: 'JambonzR0ck$',
JAMBONES_MYSQL_DATABASE: 'jambones',
JAMBONES_MYSQL_CONNECTION_LIMIT: 10,
JAMBONES_REDIS_HOST: 'jambonz.zzzzzzz.0001.usw1.cache.amazonaws.com',
JAMBONES_REDIS_PORT: 6379,
ENABLE_METRICS: 1,
STATS_HOST: '127.0.0.1',
STATS_PORT: 8125,
STATS_PROTOCOL: 'tcp',
STATS_TELEGRAF: 1,
}
}]
};
```
"drachtio": {
"port": 3001,
"secret": "cymru"
},
```
the `drachtio` object specifies the port to listen on for tcp connections from drachtio servers as well as the shared secret that is used to authenticate to the server.
> Note: either inbound or [outbound connections](https://drachtio.org/docs#outbound-connections) may be used, depending on the configuration supplied. In production, it is the intent to use outbound connections for easier centralization and clustering of application logic, while inbound connections are used for the automated test suite.
##### rtpengine location
```
"rtpengine": {
"host": "127.0.0.1",
"port": 22222
},
```
the `rtpengine` object specifies the location of the rtpengine, which will typically be running on the same server as drachtio.
##### redis location
```
"redis": {
"port": 6379,
"host": "127.0.0.1"
},
```
the `redis` object specifies the ip/dns and port that redis is listening on.
##### application log level
```
"logging": {
"level": "info"
}
```
##### transcoding options
The transcoding options for rtpengine are found in the configuration file, however these should not need to be modified.
```
"transcoding": {
"rtpCharacteristics" : {
"transport protocol": "RTP/AVP",
"DTLS": "off",
"SDES": "off",
"ICE": "remove",
"rtcp-mux": ["demux"]
},
"srtpCharacteristics": {
"transport-protocol": "UDP/TLS/RTP/SAVPF",
"ICE": "force",
"SDES": "off",
"flags": ["generate mid", "SDES-no"],
"rtcp-mux": ["require"]
}
}
```
#### Running the test suite
To run the included test suite, you will need to have a mysql server installed on your laptop/server. You will need to set the MYSQL_ROOT_PASSWORD env variable to the mysql root password before running the tests. The test suite creates a database named 'jambones_test' in your mysql server to run the tests against, and removes it when done.
To run the included test suite, you will need to have docker installed on your laptop.
```
MYSQL_ROOT_PASSWORD=foobar npm test
npm test
```
+112 -13
View File
@@ -6,37 +6,100 @@ 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_RTPENGINES, 'missing JAMBONES_RTPENGINES env var');
assert.ok(process.env.JAMBONES_NETWORK_CIDR, '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 opts = Object.assign({
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
const logger = require('pino')(opts);
const {
writeCdrs,
queryCdrs,
writeAlerts,
AlertType
} = require('@jambonz/time-series')(logger, {
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
});
const StatsCollector = require('@jambonz/stats-collector');
const stats = srf.locals.stats = new StatsCollector(logger);
const {route, setLogger} = require('./lib/middleware');
const stats = new StatsCollector(logger);
const CallSession = require('./lib/call-session');
const {performLcr, lookupAllTeamsFQDNs, lookupAccountBySipRealm} = require('@jambonz/db-helpers')({
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = [];
const {
performLcr,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
} = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER,
password: process.env.JAMBONES_MYSQL_PASSWORD,
database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
}, logger);
srf.locals.dbHelpers = {performLcr, lookupAllTeamsFQDNs, lookupAccountBySipRealm};
const {getRtpEngine} = require('@jambonz/rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, {
emitter: srf.locals.stats
const {createHash, retrieveHash, incrKey, decrKey, retrieveSet} = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
const activeCallIds = new Map();
srf.locals = {...srf.locals,
stats,
writeCdrs,
writeAlerts,
AlertType,
queryCdrs,
activeCallIds,
dbHelpers: {
performLcr,
lookupAllTeamsFQDNs,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
},
realtimeDbHelpers: {
createHash,
retrieveHash,
incrKey,
decrKey
}
};
const {initLocals, checkLimits, route} = require('./lib/middleware')(srf, logger, {
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
});
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {
emitter: stats,
dtmfListenPort: process.env.DTMF_LISTEN_PORT || 22225
});
srf.locals.getRtpEngine = getRtpEngine;
const activeCallIds = srf.locals.activeCallIds = new Set();
if (process.env.DRACHTIO_HOST) {
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}`);
const hostports = hp.split(',');
for (const hp of hostports) {
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
if (arr && 'udp' === arr[1] && !matcher.contains(arr[2])) {
logger.info(`sbc public address: ${arr[2]}`);
srf.locals.sipAddress = arr[2];
}
else if (arr && 'tcp' === arr[1] && matcher.contains(arr[2])) {
const hostport = `${arr[2]}:${arr[3]}`;
logger.info(`sbc private address: ${hostport}`);
srf.locals.privateSipAddress = hostport;
}
}
});
}
else {
@@ -48,19 +111,55 @@ if (process.env.NODE_ENV === 'test') {
});
}
srf.use('invite', [setLogger(logger), route({
host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379
})]);
srf.use('invite', [initLocals, checkLimits, route]);
srf.invite((req, res) => {
const session = new CallSession(logger, req, res);
session.connect();
});
/* update call stats periodically */
setInterval(() => {
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:outbound']);
}, 5000);
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;
};
/* update rtpengines periodically */
if (process.env.JAMBONES_RTPENGINES) {
setRtpEngines([process.env.JAMBONES_RTPENGINES]);
}
else {
const getActiveRtpServers = async() => {
try {
const set = await retrieveSet(setNameRtp);
const newArray = Array.from(set);
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
if (!arrayCompare(newArray, rtpServers)) {
logger.info({newArray}, 'resetting active rtpengines');
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
rtpServers.length = 0;
Array.prototype.push.apply(rtpServers, newArray);
}
} catch (err) {
logger.error({err}, 'Error setting new rtpengines');
}
};
setInterval(() => {
getActiveRtpServers();
}, 30000);
getActiveRtpServers();
}
const {pingMsTeamsGateways} = require('./lib/utils');
pingMsTeamsGateways(logger, srf);
+1
View File
@@ -3,5 +3,6 @@
"DTLS": "off",
"SDES": "off",
"ICE": "remove",
"flags": ["media handover", "port latching"],
"rtcp-mux": ["demux"]
}
+1 -1
View File
@@ -3,7 +3,7 @@
"transport-protocol": "UDP/TLS/RTP/SAVPF",
"ICE": "force",
"SDES": "off",
"flags": ["generate mid", "SDES-no", "media handover"],
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
"rtcp-mux": ["require"]
},
"teams": {
+308 -59
View File
@@ -1,10 +1,11 @@
const Emitter = require('events');
const {makeRtpEngineOpts} = require('./utils');
const {makeRtpEngineOpts, makeCallCountKey} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
const {SipError} = require('drachtio-srf');
const {parseUri} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-outbound');
const makeInviteInProgressKey = (callid) => `sbc-out-iip${callid}`;
/**
* this is to make sure the outgoing From has the number in the incoming From
* and not the incoming PAI
@@ -24,6 +25,29 @@ const createBLegToHeader = (req, teams) => {
return `sip:anonymous@${host}`;
};
const initCdr = (srf, req) => {
const uri = parseUri(req.uri);
const regex = /^\+(\d+)$/;
let arr = regex.exec(req.calledNumber);
const to = arr ? arr[1] : req.calledNumber;
arr = regex.exec(req.callingNumber);
const from = arr ? arr[1] : req.callingNumber;
return {
account_sid: req.get('X-Account-Sid'),
call_sid: req.get('X-Call-Sid'),
sip_callid: req.get('Call-ID'),
from,
to,
duration: 0,
answered: false,
attempted_at: Date.now(),
direction: 'outbound',
host: srf.locals.sipAddress,
remote_host: uri.host
};
};
class CallSession extends Emitter {
constructor(logger, req, res) {
super();
@@ -35,6 +59,19 @@ class CallSession extends Emitter {
this.useWss = req.locals.registration && req.locals.registration.protocol === 'wss';
this.stats = this.srf.locals.stats;
this.activeCallIds = this.srf.locals.activeCallIds;
this.writeCdrs = this.srf.locals.writeCdrs;
this.incrKey = req.srf.locals.realtimeDbHelpers.incrKey;
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
this.callCountKey = makeCallCountKey(req.locals.account_sid);
}
get account_sid() {
return this.req.locals.account_sid;
}
get privateSipAddress() {
return this.srf.locals.privateSipAddress;
}
async connect() {
@@ -42,27 +79,45 @@ class CallSession extends Emitter {
const engine = this.srf.locals.getRtpEngine();
if (!engine) {
this.logger.info('No available rtpengines, rejecting call!');
const tags = ['accepted:no', 'sipStatus:408'];
this.stats.increment('sbc.originations', tags);
return this.res.send(480);
}
debug(`got engine: ${JSON.stringify(engine)}`);
const {offer, answer, del} = engine;
const {
offer,
answer,
del,
blockMedia,
unblockMedia,
blockDTMF,
unblockDTMF,
subscribeDTMF,
unsubscribeDTMF
} = engine;
const {createHash, retrieveHash} = this.srf.locals.realtimeDbHelpers;
this.offer = offer;
this.answer = answer;
this.del = del;
this.blockMedia = blockMedia;
this.unblockMedia = unblockMedia;
this.blockDTMF = blockDTMF;
this.unblockDTMF = unblockDTMF;
this.subscribeDTMF = subscribeDTMF;
this.unsubscribeDTMF = unsubscribeDTMF;
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, this.useWss || teams, teams);
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
let proxy, uris;
const mapGateways = new Map();
try {
// determine where to send the call
debug(`connecting call: ${JSON.stringify(this.req.locals)}`);
const headers = {
let headers = {
'From': createBLegFromHeader(this.req, teams),
'Contact': createBLegFromHeader(this.req, teams),
'To': createBLegToHeader(this.req, teams),
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK'
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK',
'X-Account-Sid': this.account_sid
};
if (this.req.locals.registration) {
@@ -72,7 +127,7 @@ class CallSession extends Emitter {
uris = [contact];
}
else {
proxy = this.req.locals.registration.contact;
proxy = this.req.locals.registration.proxy;
uris = [this.req.uri];
}
}
@@ -86,9 +141,10 @@ class CallSession extends Emitter {
uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com;${vmailParam}`];
}
else uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com`];
Object.assign(headers, {
headers = {
...headers,
Contact: `sip:${this.req.calledNumber}@${this.req.get('X-MS-Teams-Tenant-FQDN')}:5061;transport=tls`
});
};
}
else {
debug('calling lcr');
@@ -100,56 +156,140 @@ class CallSession extends Emitter {
const routableNumber = this.req.calledNumber.startsWith('+') ?
this.req.calledNumber.slice(1) :
this.req.calledNumber;
uris = await this.performLcr(routableNumber);
if (!uris || uris.length === 0) throw new Error('no routes found');
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');
const tags = ['accepted:no', 'sipStatus:488'];
this.stats.increment('sbc.originations', tags);
return this.res.send(488);
}
debug(`sending call to PSTN ${uris}`);
}
// rtpengine 'offer'
debug('sending offer command to rtpengine');
const response = await this.offer(this.rtpEngineOpts.offer, {sdp: this.req.body});
const opts = {
...this.rtpEngineOpts.common,
...this.rtpEngineOpts.uac.mediaOpts,
'from-tag': this.rtpEngineOpts.uas.tag,
direction: ['private', 'public'],
sdp: this.req.body
};
const response = await this.offer(opts);
debug(`response from rtpengine to offer ${JSON.stringify(response)}`);
this.logger.debug({offer: this.rtpEngineOpts.offer, response}, 'initial offer to rtpengine');
this.logger.debug({offer: opts, response}, 'initial offer to rtpengine');
if ('ok' !== response.result) {
this.logger.error(`rtpengine offer failed with ${JSON.stringify(response)}`);
throw new Error('rtpengine failed: answer');
}
/* check if call was abandoned */
if (this.req.canceled) throw new Error('abandoned');
// crank through the list of gateways until connected, exhausted or caller hangs up
let earlyMedia = false;
while (uris.length) {
let hdrs = { ...headers};
const uri = uris.shift();
const gw = mapGateways.get(uri);
const passFailure = 0 === uris.length; // only a single target
debug(`sending INVITE to ${uri} via ${proxy})`);
this.logger.info(`sending INVITE to ${uri}`);
if (0 === uris.length) {
try {
const key = makeInviteInProgressKey(this.req.get('Call-ID'));
const obj = await retrieveHash(key);
if (obj && obj.callId && obj.cseq) {
hdrs = {
...hdrs,
'Call-ID': obj.callId,
'CSeq': `${obj.cseq} INVITE`
};
}
} catch (err) {
this.logger.info({err}, 'Error retrieving iip key');
}
}
if (gw) {
this.logger.info({gw}, `sending INVITE to ${uri} via carrier ${gw.name}`);
hdrs = {...hdrs, 'To': uri};
if (gw.diversion) {
let div = gw.diversion;
if (div.startsWith('+')) {
div = `<sip:${div}@${gw.hostport}>;reason=unknown;counter=1;privacy=off`;
}
else div = `<sip:+${div}@${gw.hostport}>;reason=unknown;counter=1;privacy=off`;
hdrs = {
...hdrs,
'Diversion': div
};
}
}
else this.logger.info(`sending INVITE to ${uri} via proxy ${proxy})`);
try {
const responseHeaders = this.privateSipAddress ? {Contact: `<sip:${this.privateSipAddress}>`} : {};
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'],
headers,
proxyResponseHeaders: ['all', '-Allow', '-Session-Expires'],
headers: hdrs,
responseHeaders,
auth: gw ? gw.auth : undefined,
localSdpB: response.sdp,
localSdpA: async(sdp, res) => {
this.toTag = res.getParsedHeader('To').params.tag;
const opts = Object.assign(this.rtpEngineOpts.answer, {sdp, 'to-tag': this.toTag});
this.rtpEngineOpts.uac.tag = res.getParsedHeader('To').params.tag;
const opts = {
...this.rtpEngineOpts.common,
...this.rtpEngineOpts.uas.mediaOpts,
'from-tag': this.rtpEngineOpts.uas.tag,
'to-tag': this.rtpEngineOpts.uac.tag,
flags: ['single codec'],
sdp
};
const response = await this.answer(opts);
this.logger.debug({answer: opts, response}, 'rtpengine answer');
if ('ok' !== response.result) {
this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`);
throw new Error('rtpengine failed: answer');
/* note: this can happen if call was abandoned while we were waiting for B leg to answer */
this.logger.info(`rtpengine answer failed with ${JSON.stringify(response)}`);
throw new Error(`rtpengine failed: ${response['error-reason']}`);
}
return response.sdp;
}
}, {
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';
else if (this.req.locals.registration) trunk = 'user';
else trunk = 'sipUri';
}
if (!this.req.locals.account.disable_cdrs) {
this.req.locals.cdr = {
...initCdr(this.req.srf, inv),
account_sid: this.req.locals.account_sid,
trunk
};
}
const opts = {
callId: inv.get('Call-ID'),
cseq: ++inv.getParsedHeader('CSeq').seq
};
try {
const key = makeInviteInProgressKey(this.req.get('Call-ID'));
await createHash(key, opts, 5);
} catch (err) {
this.logger.error({err}, 'Error saving Call-ID/CSeq');
}
},
cbProvisional: (response) => {
if (!earlyMedia && [180, 183].includes(response.status) && response.body) earlyMedia = true;
}
@@ -170,15 +310,24 @@ class CallSession extends Emitter {
!(err instanceof SipError) || // unexpected error
err.status === 487) { // caller hung up
if (err instanceof SipError) this.logger.info(`final call failure ${err.status}`);
else this.logger.error(err, 'unexpected call failure');
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');
debug(`got final outdial error: ${err}`);
if (!passFailure) this.res.send(err.status || 500);
if (!passFailure) this.res.send(status);
this.emit('failed');
this.rtpEngineResource.destroy();
const tags = ['accepted:no', `sipStatus:${err.status || 500}`];
const tags = ['accepted:no', `sipStatus:${status}`];
this.stats.increment('sbc.originations', tags);
break;
if (this.req.locals.cdr && ![401, 407].includes(status)) {
this.writeCdrs({...this.req.locals.cdr,
terminated_at: Date.now(),
termination_reason: 487 === status ? 'caller abandoned' : 'failed',
sip_status: status,
}).catch((err) => this.logger.error({err}, 'Error writing cdr for call failure'));
}
}
else {
this.logger.info(`got ${err.status}, cranking back to next destination`);
@@ -186,48 +335,139 @@ class CallSession extends Emitter {
}
}
} catch (err) {
this.logger.error(err, `Error setting up outbonund call to: ${uris}`);
if ('abandonded' !== err.message) this.logger.error(err, `Error setting up outbonund call to: ${uris}`);
this.emit('failed');
this.rtpEngineResource.destroy();
}
}
_setHandlers({uas, uac}) {
this.activeCallIds.add(this.req.get('Call-ID'));
const callStart = Date.now();
const tags = ['accepted:yes', 'sipStatus:200'];
this.stats.increment('sbc.originations', tags);
this.activeCallIds.set(this.req.get('Call-ID'), this);
if (this.req.locals.cdr) {
this.req.locals.cdr = {
...this.req.locals.cdr,
answered: true,
answered_at: callStart
};
}
this.uas = uas;
this.uac = uac;
[uas, uac].forEach((dlg) => {
//hangup
dlg.on('destroy', () => {
this.logger.info('call ended');
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();
this.decrKey(this.callCountKey)
.then((count) => {
this.logger.debug(`after hangup there are ${count} active calls for this account`);
debug(`after hangup there are ${count} active calls for this account`);
return;
})
.catch((err) => this.logger.error({err}, 'Error decrementing call count'));
/* write cdr for connected call */
if (this.req.locals.cdr) {
const now = Date.now();
this.writeCdrs({...this.req.locals.cdr,
terminated_at: now,
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
sip_status: 200,
answered: true,
duration: Math.floor((now - callStart) / 1000)
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
}
});
});
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uac.tag,
this._onDTMF.bind(this, uas));
uas.on('modify', this._onReinvite.bind(this, uas));
uac.on('modify', this._onReinvite.bind(this, uac));
uac.on('modify', this._onNetworkReinvite.bind(this, uac));
uas.on('refer', this._onFeatureServerTransfer.bind(this, uas));
uas.on('info', this._onInfo.bind(this, uas));
uac.on('info', this._onInfo.bind(this, uac));
// default forwarding of other request types
forwardInDialogRequests(uac, ['info', 'notify', 'options', 'message']);
forwardInDialogRequests(uac, ['notify', 'options', 'message']);
}
async _onDTMF(dlg, payload) {
this.logger.info({payload}, '_onDTMF');
try {
let dtmf;
switch (payload.event) {
case 10:
dtmf = '*';
break;
case 11:
dtmf = '#';
break;
default:
dtmf = '' + payload.event;
break;
}
await dlg.request({
method: 'INFO',
headers: {
'Content-Type': 'application/dtmf-relay'
},
body: `Signal=${dtmf}
Duration=${payload.duration} `
});
} catch (err) {
this.logger.info({err}, 'Error sending INFO application/dtmf-relay');
}
}
async _onReinvite(dlg, req, res) {
try {
let response = await this.offer(Object.assign(this.rtpEngineOpts.offer, {sdp: req.body}));
const reason = req.get('X-Reason');
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
const answerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uas.mediaOpts : this.rtpEngineOpts.uac.mediaOpts;
const direction = dlg.type === 'uas' ? ['private', 'public'] : ['public', 'private'];
let opts = {
...this.rtpEngineOpts.common,
...offerMedia,
'from-tag': fromTag,
'to-tag': toTag,
direction,
sdp: req.body,
};
if (reason) opts.flags.push('reset');
let response = await this.offer(opts);
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
}
const sdp = await dlg.other.modify(response.sdp);
const opts = Object.assign(this.rtpEngineOpts.answer, {sdp, 'to-tag': res.getParsedHeader('To').params.tag});
/* if this is a re-invite from the FS to change media anchoring, avoid sending the reinvite out */
let sdp;
if (reason && dlg.type === 'uas' && ['release-media', 'anchor-media'].includes(reason)) {
this.logger.info(`got a reinvite from FS to ${reason}`);
sdp = dlg.other.remote.sdp;
}
else {
sdp = await dlg.other.modify(response.sdp);
}
opts = {
...this.rtpEngineOpts.common,
...answerMedia,
'from-tag': fromTag,
'to-tag': toTag,
sdp
};
response = await this.answer(opts);
if ('ok' !== response.result) {
res.send(488);
@@ -239,29 +479,40 @@ class CallSession extends Emitter {
}
}
async _onNetworkReinvite(dlg, req, res) {
async _onInfo(dlg, req, res) {
try {
const newAnswerOpts = Object.assign({}, this.rtpEngineOpts.answer, {sdp: req.body});
let response = await this.answer(newAnswerOpts);
this.logger.debug({answer: newAnswerOpts, response}, '_onNetworkReinvite: answer to rtpengine');
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
}
if (dlg.type === 'uas' && req.has('X-Reason')) {
const toTag = this.rtpEngineOpts.uac.tag;
const reason = req.get('X-Reason');
const opts = {
...this.rtpEngineOpts.common,
'from-tag': toTag
};
this.logger.info(`_onInfo: got request ${reason}`);
res.send(200);
// reinvite feature server
const sdp = await dlg.other.modify(response.sdp);
const newOfferOpts = Object.assign({}, this.rtpEngineOpts.offer, {sdp, 'to-tag': this.toTag});
response = await this.offer(newOfferOpts);
this.logger.debug({answer: newOfferOpts, response}, '_onNetworkReinvite: offer to rtpengine');
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
if (reason.startsWith('mute')) {
const response = Promise.all([this.blockMedia(opts), this.blockDTMF(opts)]);
this.logger.info({response}, `_onInfo: response to rtpengine command for ${reason}`);
}
else if (reason.startsWith('unmute')) {
const response = Promise.all([this.unblockMedia(opts), this.unblockDTMF(opts)]);
this.logger.info({response}, `_onInfo: response to rtpengine command for ${reason}`);
}
}
else {
const response = await dlg.other.request({
method: 'INFO',
headers: req.headers,
body: req.body
});
res.send(response.status, {
headers: response.headers,
body: response.body
});
}
res.send(200, {body: response.sdp});
} catch (err) {
this.logger.error(err, 'Error handling reinvite');
this.logger.info({err}, `Error handing INFO request on ${dlg.type} leg`);
}
}
@@ -283,8 +534,6 @@ class CallSession extends Emitter {
Object.assign(headers, {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')});
}
const dlg = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
this.uas.destroy();
this.uas = dlg;
this.uas.other = this.uac;
this.uac.other = this.uas;
+126 -38
View File
@@ -1,50 +1,140 @@
const debug = require('debug')('jambonz:sbc-outbound');
const parseUri = require('drachtio-srf').parseUri;
const srf = require('drachtio-srf');
const Registrar = require('jambonz-mw-registrar');
const {selectHostPort} = require('./utils');
const Registrar = require('@jambonz/mw-registrar');
const {selectHostPort, makeCallCountKey} = require('./utils');
function setLogger(logger) {
return (req, res, next) => {
debug('setting logger');
req.locals = req.locals || {};
req.locals.logger = logger.child({callId: req.get('Call-ID')});
req.srf.locals.stats.increment('sbc.invites', ['direction:outbound']);
next();
};
}
function isLocalUri(host, req) {
const isLocalUri = (host, req) => {
debug({hostport: req.server.hostport}, `is ${host} local?`);
return req.server.hostport.includes(host);
}
};
function route(opts) {
module.exports = (srf, logger, opts) => {
const {incrKey, decrKey} = srf.locals.realtimeDbHelpers;
const {stats} = srf.locals;
const registrar = new Registrar(opts);
return async(req, res, next) => {
const {
lookupAccountCapacitiesBySid,
lookupAccountBySid
} = srf.locals.dbHelpers;
const initLocals = async(req, res, next) => {
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});
if (!req.locals.account_sid) {
logger.info('missing X-Account-Sid on outbound call');
return res.send(403, {
headers: {
'X-Reason': 'missing X-Account-Sid'
}
});
}
stats.increment('sbc.invites', ['direction:outbound']);
req.on('cancel', () => {
req.locals.logger.info({callId}, 'caller hungup before connecting');
req.canceled = true;
const tags = ['canceled:yes', 'sipStatus:487'];
if (req.locals.originator) tags.push(`originator:${req.locals.originator}`);
stats.increment('sbc.origination', tags);
});
try {
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);
}
next();
};
const checkLimits = async(req, res, next) => {
const {logger, account_sid} = req.locals;
const {writeAlerts, AlertType} = req.srf.locals;
const key = makeCallCountKey(account_sid);
try {
/* increment the call count */
const calls = await incrKey(key);
debug(`checkLimits: call count is now ${calls}`);
/* decrement count if INVITE is later rejected */
res.once('end', ({status}) => {
if (status > 200) {
debug('checkLimits: decrementing call count due to rejection');
decrKey(key)
.then((count) => {
logger.debug({key}, `after rejection there are ${count} active calls for this account`);
debug({key}, `after rejection there are ${count} active calls for this account`);
return;
})
.catch((err) => logger.error({err}, 'checkLimits: decrKey err'));
const tags = ['accepted:no', `sipStatus:${status}`];
stats.increment('sbc.originations', tags);
}
else {
const tags = ['accepted:yes', 'sipStatus:200'];
stats.increment('sbc.originations', tags);
}
});
/* compare to account's limit, though avoid db hit when call count is low */
const minLimit = process.env.MIN_CALL_LIMIT ?
parseInt(process.env.MIN_CALL_LIMIT) :
0;
if (calls <= minLimit) return next();
const capacities = await lookupAccountCapacitiesBySid(account_sid);
const limit = capacities.find((c) => c.category == 'voice_call_session');
if (!limit) {
logger.debug('checkLimits: no call limits specified');
return next();
}
const limit_sessions = limit.quantity;
if (calls > limit_sessions) {
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
writeAlerts({
alert_type: AlertType.CALL_LIMIT,
account_sid,
count: limit_sessions
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
res.send(503, 'Maximum Calls In Progress');
}
next();
} catch (err) {
logger.error({err}, 'error checking limits error for inbound call');
res.send(500);
}
};
const route = async(req, res, next) => {
const logger = req.locals.logger;
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);
if (!uri.user || !uri.host) {
if (!uri || !uri.user || !uri.host) {
logger.info({uri: req.uri}, 'invalid request-uri on outbound call, rejecting');
res.send(404, {
res.send(400, {
headers: {
'X-Reason': 'invalid request-uri'
}
});
const tags = ['accepted:no', 'sipStatus:404'];
req.srf.locals.stats.increment('sbc.originations', tags);
return;
}
const aor = `${uri.user}@${uri.host}`;
req.locals = req.locals || {};
debug(`received outbound INVITE to ${req.calledNumber} from server at ${req.server.hostport}`);
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')) {
logger.debug('This is a call to ms teams');
req.locals.target = 'teams';
@@ -52,7 +142,7 @@ function route(opts) {
}
else if (!dotDecimalHost) {
// uri host is not a dot-decimal address, so try to look up user
debug(`searching for registered user ${aor}`);
logger.debug(`searching for registered user ${aor}`);
reg = await registrar.query(aor);
if (reg) {
// user is registered..find out which sbc is handling it
@@ -60,12 +150,11 @@ function route(opts) {
// other sbc => proxy the call there
logger.info({details: reg}, `sending call to registered user ${aor}`);
if (req.server.hostport !== reg.sbcAddress) {
//proxy
const proxyAddress = selectHostPort(reg.sbcAddress, 'udp');
logger.info(`proxying call to SBC at ${proxyAddress}`);
//req.proxy(`sip:${proxyAddress[1]}`);
req.srf.proxyRequest(req, `sip:${proxyAddress[1]}`);
return;
/* 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}`);
return res.send(302, {headers: {Contact: redirectUri}});
}
req.locals.registration = reg;
req.locals.target = 'user';
@@ -92,8 +181,6 @@ function route(opts) {
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`);
const tags = ['accepted:no', 'sipStatus:404'];
req.srf.locals.stats.increment('sbc.originations', tags);
return res.send(404);
}
@@ -101,9 +188,10 @@ function route(opts) {
req.locals.target = 'lcr';
next();
};
}
module.exports = {
setLogger,
route
return {
initLocals,
checkLimits,
route
};
};
+25 -17
View File
@@ -5,22 +5,26 @@ const debug = require('debug')('jambonz:sbc-outbound');
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
const from = req.getParsedHeader('from');
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
const common = {'call-id': req.get('Call-ID'), 'from-tag': from.params.tag};
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCharacteristics;
const srcOpts = srcIsUsingSrtp ? srtpOpts : rtpCharacteristics;
const common = {
'call-id': req.get('Call-ID'),
'replace': ['origin', 'session-connection']
};
return {
common,
offer: Object.assign(
{'sdp': req.body, 'replace': ['origin', 'session-connection']},
{'direction': [ 'private', 'public']},
common,
dstIsUsingSrtp ? srtpOpts : rtpCharacteristics),
answer: Object.assign(
{'replace': ['origin', 'session-connection']},
common,
srcIsUsingSrtp ? srtpOpts : rtpCharacteristics)
uas: {
tag: from.params.tag,
mediaOpts: srcOpts
},
uac: {
tag: null,
mediaOpts: dstOpts
}
};
}
function selectHostPort(hostport, protocol) {
const selectHostPort = (hostport, protocol) => {
debug(`selectHostPort: ${hostport}, ${protocol}`);
const sel = hostport
.split(',')
@@ -32,9 +36,9 @@ function selectHostPort(hostport, protocol) {
return hp[0] === protocol && hp[1] !== '127.0.0.1';
});
return sel[0];
}
};
function pingMs(logger, srf, gateway, fqdns) {
const pingMs = (logger, srf, gateway, fqdns) => {
const uri = `sip:${gateway}`;
const proxy = `sip:${gateway}:5061;transport=tls`;
fqdns.forEach((fqdn) => {
@@ -48,8 +52,9 @@ function pingMs(logger, srf, gateway, fqdns) {
}
}).catch((err) => logger.error(err, `Error pinging MS Teams at ${gateway}`));
});
}
function pingMsTeamsGateways(logger, srf) {
};
const pingMsTeamsGateways = (logger, srf) => {
const {lookupAllTeamsFQDNs} = srf.locals.dbHelpers;
lookupAllTeamsFQDNs()
.then((fqdns) => {
@@ -64,10 +69,13 @@ function pingMsTeamsGateways(logger, srf) {
.catch((err) => {
logger.error(err, 'Error looking up all ms teams fqdns');
});
}
};
const makeCallCountKey = (sid) => `${sid}:outcalls`;
module.exports = {
makeRtpEngineOpts,
selectHostPort,
pingMsTeamsGateways
pingMsTeamsGateways,
makeCallCountKey
};
+5645 -837
View File
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -1,6 +1,6 @@
{
"name": "sbc-outbound",
"version": "0.4.3",
"version": "0.6.7-rc5",
"main": "app.js",
"engines": {
"node": ">= 8.10.0"
@@ -22,26 +22,27 @@
"description": "jambonz session border controller application for outbound calls",
"scripts": {
"start": "node app",
"test": "NODE_ENV=test JAMBONES_MYSQL_HOST=localhost JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 node test/ | ./node_modules/.bin/tap-spec",
"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/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib"
},
"dependencies": {
"@jambonz/db-helpers": "^0.5.1",
"@jambonz/rtpengine-utils": "^0.1.7",
"@jambonz/stats-collector": "0.1.1",
"debug": "^4.1.1",
"@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",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.1",
"drachtio-fn-b2b-sugar": "^0.0.12",
"drachtio-srf": "^4.4.39",
"jambonz-mw-registrar": "^0.1.3",
"pino": "^6.5.1",
"rtpengine-client": "^0.1.1"
"drachtio-srf": "^4.4.55",
"pino": "^6.11.2"
},
"devDependencies": {
"blue-tape": "^1.0.0",
"eslint": "^7.12.0",
"eslint": "^7.18.0",
"eslint-plugin-promise": "^4.2.1",
"nyc": "^15.1.0",
"tap-spec": "^5.0.0"
"tape": "^5.2.2"
}
}
+5 -6
View File
@@ -1,10 +1,9 @@
const test = require('blue-tape');
//const test = require('tape').test ;
const test = require('tape');
const exec = require('child_process').exec ;
const pwd = process.env.TRAVIS ? '' : '-p$MYSQL_ROOT_PASSWORD';
const pwd = '-p$MYSQL_ROOT_PASSWORD';
test('creating jambones_test database', (t) => {
exec(`mysql -h localhost -u root ${pwd} < ${__dirname}/db/create_test_db.sql`, (err, stdout, stderr) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp < ${__dirname}/db/create_test_db.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('database successfully created');
t.end();
@@ -12,7 +11,7 @@ test('creating jambones_test database', (t) => {
});
test('creating schema', (t) => {
exec(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/jambones-sql.sql`, (err, stdout, stderr) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/jambones-sql.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('schema successfully created');
t.end();
@@ -20,7 +19,7 @@ test('creating schema', (t) => {
});
test('populating test case data', (t) => {
exec(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/populate-test-data.sql`, (err, stdout, stderr) => {
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/populate-test-data.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('test data set created');
t.end();
+2 -2
View File
@@ -1,3 +1,3 @@
create database jambones_test;
create user jambones_test@localhost IDENTIFIED WITH mysql_native_password by 'jambones_test';
grant all on jambones_test.* to jambones_test@localhost;
create user jambones_test@'%' IDENTIFIED WITH mysql_native_password by 'jambones_test';
grant all on jambones_test.* to jambones_test@'%';
+347 -58
View File
@@ -2,20 +2,46 @@
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS account_static_ips;
DROP TABLE IF EXISTS account_products;
DROP TABLE IF EXISTS account_subscriptions;
DROP TABLE IF EXISTS beta_invite_codes;
DROP TABLE IF EXISTS call_routes;
DROP TABLE IF EXISTS dns_records;
DROP TABLE IF EXISTS lcr_carrier_set_entry;
DROP TABLE IF EXISTS lcr_routes;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS predefined_sip_gateways;
DROP TABLE IF EXISTS ms_teams_tenants;
DROP TABLE IF EXISTS predefined_carriers;
DROP TABLE IF EXISTS account_offers;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS sbc_addresses;
DROP TABLE IF EXISTS ms_teams_tenants;
DROP TABLE IF EXISTS signup_history;
DROP TABLE IF EXISTS smpp_addresses;
DROP TABLE IF EXISTS speech_credentials;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS smpp_gateways;
DROP TABLE IF EXISTS phone_numbers;
DROP TABLE IF EXISTS sip_gateways;
@@ -30,6 +56,41 @@ DROP TABLE IF EXISTS service_providers;
DROP TABLE IF EXISTS webhooks;
CREATE TABLE account_static_ips
(
account_static_ip_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
public_ipv4 VARCHAR(16) NOT NULL UNIQUE ,
private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
PRIMARY KEY (account_static_ip_sid)
);
CREATE TABLE account_subscriptions
(
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
pending BOOLEAN NOT NULL DEFAULT false,
effective_start_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
effective_end_date DATETIME,
change_reason VARCHAR(255),
stripe_subscription_id VARCHAR(56),
stripe_payment_method_id VARCHAR(56),
stripe_statement_descriptor VARCHAR(255),
last4 VARCHAR(512),
exp_month INTEGER,
exp_year INTEGER,
card_type VARCHAR(16),
pending_reason VARBINARY(52),
PRIMARY KEY (account_subscription_sid)
);
CREATE TABLE beta_invite_codes
(
invite_code CHAR(6) NOT NULL UNIQUE ,
in_use BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (invite_code)
);
CREATE TABLE call_routes
(
call_route_sid CHAR(36) NOT NULL UNIQUE ,
@@ -38,7 +99,16 @@ account_sid CHAR(36) NOT NULL,
regex VARCHAR(255) NOT NULL,
application_sid CHAR(36) NOT NULL,
PRIMARY KEY (call_route_sid)
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
) COMMENT='a regex-based pattern match for call routing';
CREATE TABLE dns_records
(
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
record_type VARCHAR(6) NOT NULL,
record_id INTEGER NOT NULL,
PRIMARY KEY (dns_record_sid)
);
CREATE TABLE lcr_routes
(
@@ -49,17 +119,81 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
PRIMARY KEY (lcr_route_sid)
) COMMENT='Least cost routing table';
CREATE TABLE predefined_carriers
(
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
requires_static_ip BOOLEAN NOT NULL DEFAULT false,
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
requires_register BOOLEAN NOT NULL DEFAULT false,
register_username VARCHAR(64),
register_sip_realm VARCHAR(64),
register_password VARCHAR(64),
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
inbound_auth_username VARCHAR(64),
inbound_auth_password VARCHAR(64),
diversion VARCHAR(32),
PRIMARY KEY (predefined_carrier_sid)
);
CREATE TABLE predefined_sip_gateways
(
predefined_sip_gateway_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
port INTEGER NOT NULL DEFAULT 5060 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',
netmask INTEGER NOT NULL DEFAULT 32,
predefined_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (predefined_sip_gateway_sid)
);
CREATE TABLE products
(
product_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(32) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
PRIMARY KEY (product_sid)
);
CREATE TABLE account_products
(
account_product_sid CHAR(36) NOT NULL UNIQUE ,
account_subscription_sid CHAR(36) NOT NULL,
product_sid CHAR(36) NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (account_product_sid)
);
CREATE TABLE account_offers
(
account_offer_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
product_sid CHAR(36) NOT NULL,
stripe_product_id VARCHAR(56) NOT NULL,
PRIMARY KEY (account_offer_sid)
);
CREATE TABLE api_keys
(
api_key_sid CHAR(36) NOT NULL UNIQUE ,
token CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36),
service_provider_sid CHAR(36),
expires_at TIMESTAMP NULL,
last_used TIMESTAMP NULL,
expires_at TIMESTAMP NULL DEFAULT NULL,
last_used TIMESTAMP NULL DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (api_key_sid)
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
) COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE sbc_addresses
(
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 5060,
service_provider_sid CHAR(36),
PRIMARY KEY (sbc_address_sid)
);
CREATE TABLE ms_teams_tenants
(
@@ -71,60 +205,121 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
PRIMARY KEY (ms_teams_tenant_sid)
) COMMENT='A Microsoft Teams customer tenant';
CREATE TABLE sbc_addresses
CREATE TABLE signup_history
(
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
email VARCHAR(255) NOT NULL,
name VARCHAR(255),
signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (email)
);
CREATE TABLE smpp_addresses
(
smpp_address_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 5060,
use_tls BOOLEAN NOT NULL DEFAULT 0,
is_primary BOOLEAN NOT NULL DEFAULT 1,
service_provider_sid CHAR(36),
PRIMARY KEY (sbc_address_sid)
PRIMARY KEY (smpp_address_sid)
);
CREATE TABLE speech_credentials
(
speech_credential_sid CHAR(36) NOT NULL UNIQUE ,
service_provider_sid CHAR(36),
account_sid CHAR(36),
vendor VARCHAR(32) NOT NULL,
credential VARCHAR(8192) NOT NULL,
use_for_tts BOOLEAN DEFAULT true,
use_for_stt BOOLEAN DEFAULT true,
last_used DATETIME,
last_tested DATETIME,
tts_tested_ok BOOLEAN,
stt_tested_ok BOOLEAN,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (speech_credential_sid)
);
CREATE TABLE users
(
user_sid CHAR(36) NOT NULL UNIQUE ,
name CHAR(36) NOT NULL UNIQUE ,
hashed_password VARCHAR(1024) NOT NULL,
salt CHAR(16) NOT NULL,
force_change BOOLEAN NOT NULL DEFAULT TRUE,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
pending_email VARCHAR(255),
phone VARCHAR(20) UNIQUE ,
hashed_password VARCHAR(1024),
account_sid CHAR(36),
service_provider_sid CHAR(36),
force_change BOOLEAN NOT NULL DEFAULT FALSE,
provider VARCHAR(255) NOT NULL,
provider_userid VARCHAR(255),
scope VARCHAR(16) NOT NULL DEFAULT 'read-write',
phone_activation_code VARCHAR(16),
email_activation_code VARCHAR(16),
email_validated BOOLEAN NOT NULL DEFAULT false,
phone_validated BOOLEAN NOT NULL DEFAULT false,
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (user_sid)
);
CREATE TABLE voip_carriers
(
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
description VARCHAR(255),
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a sip trunk that is associated with a specific account',
service_provider_sid CHAR(36),
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
e164_leading_plus BOOLEAN NOT NULL DEFAULT false,
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
requires_register BOOLEAN NOT NULL DEFAULT false,
register_username VARCHAR(64),
register_sip_realm VARCHAR(64),
register_password VARCHAR(64),
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
inbound_auth_username VARCHAR(64),
inbound_auth_password VARCHAR(64),
diversion VARCHAR(32),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
smpp_system_id VARCHAR(255),
smpp_password VARCHAR(64),
smpp_enquire_link_interval INTEGER DEFAULT 0,
smpp_inbound_system_id VARCHAR(255),
smpp_inbound_password VARCHAR(64),
PRIMARY KEY (voip_carrier_sid)
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
) COMMENT='A Carrier or customer PBX that can send or receive calls';
CREATE TABLE smpp_gateways
(
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(128) NOT NULL,
port INTEGER NOT NULL DEFAULT 2775,
netmask INTEGER NOT NULL DEFAULT 32,
is_primary BOOLEAN NOT NULL DEFAULT 1,
inbound BOOLEAN NOT NULL DEFAULT 0 COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL DEFAULT 1 COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
use_tls BOOLEAN DEFAULT 0,
voip_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (smpp_gateway_sid)
);
CREATE TABLE phone_numbers
(
phone_number_sid CHAR(36) UNIQUE ,
number VARCHAR(32) NOT NULL UNIQUE ,
voip_carrier_sid CHAR(36) NOT NULL,
voip_carrier_sid CHAR(36),
account_sid CHAR(36),
application_sid CHAR(36),
service_provider_sid CHAR(36) COMMENT 'if not null, this number is a test number for the associated service provider',
PRIMARY KEY (phone_number_sid)
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
) COMMENT='An HTTP callback';
) COMMENT='A phone number that has been assigned to an account';
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',
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',
@@ -143,20 +338,33 @@ priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempt
PRIMARY KEY (lcr_carrier_set_entry_sid)
) COMMENT='An entry in the LCR routing list';
CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
) COMMENT='An HTTP callback';
CREATE TABLE applications
(
application_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
service_provider_sid CHAR(36) COMMENT 'if non-null, this application is a test application that can be used by any account under the associated service provider',
account_sid CHAR(36) COMMENT 'account that this application belongs to (if null, this is a service provider test application)',
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
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_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (application_sid)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
) COMMENT='A defined set of behaviors to be applied to phone calls ';
CREATE TABLE service_providers
(
@@ -167,7 +375,7 @@ root_domain VARCHAR(128) UNIQUE ,
registration_hook_sid CHAR(36),
ms_teams_fqdn VARCHAR(255),
PRIMARY KEY (service_provider_sid)
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
) COMMENT='A partition of the platform used by one service provider';
CREATE TABLE accounts
(
@@ -176,72 +384,151 @@ name VARCHAR(64) NOT NULL,
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
queue_event_hook_sid CHAR(36),
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
plan_type ENUM('trial','free','paid') NOT NULL DEFAULT 'trial',
stripe_customer_id VARCHAR(56),
webhook_secret VARCHAR(36) NOT NULL,
disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
trial_end_date DATETIME,
deactivated_reason VARCHAR(255),
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
PRIMARY KEY (account_sid)
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
) COMMENT='An enterprise that uses the platform for comm services';
CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_sid);
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
CREATE INDEX product_sid_idx ON products (product_sid);
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
ALTER TABLE account_products ADD FOREIGN KEY account_subscription_sid_idxfk (account_subscription_sid) REFERENCES account_subscriptions (account_subscription_sid);
ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REFERENCES products (product_sid);
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
CREATE INDEX account_sid_idx ON account_offers (account_sid);
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX product_sid_idx ON account_offers (product_sid);
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
CREATE INDEX account_sid_idx ON api_keys (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX email_idx ON signup_history (email);
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_3 (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_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX user_sid_idx ON users (user_sid);
CREATE INDEX name_idx ON users (name);
CREATE INDEX email_idx ON users (email);
CREATE INDEX phone_idx ON users (phone);
CREATE INDEX account_sid_idx ON users (account_sid);
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
CREATE INDEX name_idx ON voip_carriers (name);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
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);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_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);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
CREATE INDEX voip_carrier_sid_idx ON sip_gateways (voip_carrier_sid);
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
CREATE INDEX application_sid_idx ON applications (application_sid);
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON applications (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE applications ADD FOREIGN KEY call_status_hook_sid_idxfk (call_status_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE applications ADD FOREIGN KEY messaging_hook_sid_idxfk (messaging_hook_sid) REFERENCES webhooks (webhook_sid);
CREATE INDEX service_provider_sid_idx ON service_providers (service_provider_sid);
CREATE INDEX name_idx ON service_providers (name);
CREATE INDEX root_domain_idx ON service_providers (root_domain);
@@ -250,10 +537,12 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1;
+15 -3
View File
@@ -1,11 +1,23 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.20', true, true);
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound)
+15 -4
View File
@@ -1,18 +1,29 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
-- "good" carrier - "westco" at 172.39.0.20
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.20', true, true);
-- "bad" carrier - "eastco" at 172.39.0.21 (returns 503)
insert into voip_carriers (voip_carrier_sid, name) values ('1d8ef351-062a-4487-94f8-7698d5a40d24', 'eastco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('1d8ef351-062a-4487-94f8-7698d5a40d24', 'eastco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('e71519ff-4494-4c98-a06a-324e2712d94b', '1d8ef351-062a-4487-94f8-7698d5a40d24', '172.39.0.21', true, true);
+17 -5
View File
@@ -1,22 +1,34 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
-- "good" carrier - "westco" at 172.39.0.20
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.20', true, true);
-- "bad" carrier - "eastco" at 172.39.0.21 (returns 503)
insert into voip_carriers (voip_carrier_sid, name) values ('1d8ef351-062a-4487-94f8-7698d5a40d24', 'eastco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('1d8ef351-062a-4487-94f8-7698d5a40d24', 'eastco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('e71519ff-4494-4c98-a06a-324e2712d94b', '1d8ef351-062a-4487-94f8-7698d5a40d24', '172.39.0.21', true, true);
-- "bad" carrier - "northco" at 172.39.0.22 (returns 100 Trying and never answers)
insert into voip_carriers (voip_carrier_sid, name) values ('7b4b9c56-4d81-4f31-9d70-62cd7d82193b', 'northco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid) values ('7b4b9c56-4d81-4f31-9d70-62cd7d82193b', 'northco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('fcbb2109-582c-4d55-b38a-b6bc7cc4be73', '7b4b9c56-4d81-4f31-9d70-62cd7d82193b', '172.39.0.22', true, true);
+16 -3
View File
@@ -1,11 +1,24 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
-- one carrier, uas script expecting a reinvite
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, e164_leading_plus, requires_register, register_username, register_sip_realm, register_password)
values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 0, 1, 'foo', 'jambonz.org', 'bar');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.23', true, true);
+25
View File
@@ -0,0 +1,25 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 0);
-- one carrier, uas script expecting a reinvite
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, e164_leading_plus, requires_register, register_username, register_sip_realm, register_password)
values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 0, 1, 'foo', 'jambonz.org', 'bar');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.23', true, true);
+36
View File
@@ -8,6 +8,20 @@ networks:
- subnet: 172.39.0.0/16
services:
mysql:
image: mysql:5.7
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
timeout: 5s
retries: 10
networks:
sbc-outbound:
ipv4_address: 172.39.0.2
sbc:
image: drachtio/drachtio-server:latest
command: drachtio --contact "sip:*;transport=udp" --loglevel debug --sofia-loglevel 9
@@ -16,6 +30,9 @@ services:
networks:
sbc-outbound:
ipv4_address: 172.39.0.10
depends_on:
mysql:
condition: service_healthy
redis:
image: redis:5-alpine
@@ -89,3 +106,22 @@ services:
networks:
sbc-outbound:
ipv4_address: 172.39.0.23
sip-uri-auth:
image: drachtio/sipp:latest
command: sipp -sf /tmp/uas-auth.xml
volumes:
- ./scenarios:/tmp
tty: true
networks:
sbc-outbound:
ipv4_address: 172.39.0.24
influxdb:
image: influxdb:1.8-alpine
ports:
- "8086:8086"
networks:
sbc-outbound:
ipv4_address: 172.39.0.90
+2 -3
View File
@@ -1,11 +1,10 @@
const test = require('blue-tape');
//const test = require('tape').test ;
const test = require('tape');
const exec = require('child_process').exec ;
test('starting docker network..', (t) => {
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
t.pass('docker started');
t.end(err);
t.end(err);
});
});
+1 -11
View File
@@ -1,17 +1,7 @@
const test = require('blue-tape');
const test = require('tape');
//const test = require('tape').test ;
const exec = require('child_process').exec ;
/*
test('docker logs', (t) => {
exec('docker logs test_sbc_1', (err, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
t.end();
});
});
*/
test('stopping docker network..', (t) => {
t.timeoutAfter(10000);
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err, stdout, stderr) => {
-1
View File
@@ -1,5 +1,4 @@
require('./docker_start');
require('./create-test-db');
require('./sip-tests');
require('./remove-test-db');
require('./docker_stop');
-11
View File
@@ -1,11 +0,0 @@
const test = require('blue-tape') ;
const exec = require('child_process').exec ;
const pwd = process.env.TRAVIS ? '' : '-p$MYSQL_ROOT_PASSWORD';
test('dropping jambones_test database', (t) => {
exec(`mysql -h localhost -u root ${pwd} < ${__dirname}/db/remove_test_db.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('database successfully dropped');
t.end();
});
});
+2
View File
@@ -34,6 +34,8 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-carrier-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
Content-Type: application/sdp
Content-Length: [len]
@@ -0,0 +1,58 @@
<?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
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="503" 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-limits
Content-Length: 0
]]>
</send>
</scenario>
@@ -15,6 +15,8 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-carrier-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
Content-Type: application/sdp
Content-Length: [len]
@@ -14,6 +14,8 @@
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
Subject: uac-pcap-carrier-success
Content-Type: application/sdp
Content-Length: [len]
+2
View File
@@ -35,6 +35,8 @@
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-device-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: ff649e33-e771-403a-8c99-1780eabbc803
Content-Type: application/sdp
Content-Length: [len]
+171
View File
@@ -0,0 +1,171 @@
<?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@172.39.0.24 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@172.39.0.24>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-sip-uri-auth-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
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="401" auth="true">
</recv>
<send>
<![CDATA[
ACK sip:sip:john@172.39.0.24 SIP/2.0
[last_Via:]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:sip:john@jambonz.org>[peer_tag_param]
[last_Call-ID:]
CSeq: 1 ACK
Subject: uac-sip-uri-auth-success
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
INVITE sip:john@172.39.0.24 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]xx
To: <sip:john@172.39.0.24>
[last_Call-ID:]
CSeq: 2 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
[authentication username=foo password=bar]
Max-Forwards: 70
Subject: uac-sip-uri-auth-success
X-Account-Sid: ed649e33-e771-403a-8c99-1780eabbc803
X-Call-Sid: fff49e33-e771-403a-8c99-1780eabbc803
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@172.39.0.24 SIP/2.0
[last_Via:]
[last_From:]
To: <sip:sip:john@jambonz.org>[peer_tag_param]
[last_Call-ID:]
CSeq: 2 ACK
Subject: uac-sip-uri-auth-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:john@172.39.0.24 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-auth-success
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>
+155
View File
@@ -0,0 +1,155 @@
<?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 default 'uas' scenario. -->
<!-- -->
<scenario name="Basic UAS responder">
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv request="INVITE" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
SIP/2.0 401 Unauthorized
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
WWW-Authenticate: Digest realm="jambonz.org", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
Content-Length: 0
]]>
</send>
<recv request="ACK"
rtd="true"
crlf="true">
</recv>
<recv request="INVITE" crlf="true">
<action>
<verifyauth assign_to="authvalid" username="foo" password="bar" />
</action>
</recv>
<nop hide="true" test="authvalid" next="goodauth" />
<send retrans="500">
<![CDATA[
SIP/2.0 403 Forbidden
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>;expires=60
Content-Type: application/sdp
Content-Length: 0
]]>
</send>
<recv request="ACK" next="endit"
rtd="true"
crlf="true">
</recv>
<label id="goodauth"/>
<send>
<![CDATA[
SIP/2.0 100 Trying
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv request="ACK"
optional="true"
rtd="true"
crlf="true">
</recv>
<recv request="BYE">
</recv>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<label id="endit"/>
<!-- Keep the call open for a while in case the 200 is lost to be -->
<!-- able to retransmit it if we receive the BYE again. -->
<timewait milliseconds="4000"/>
<!-- 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>
+51 -21
View File
@@ -2,14 +2,60 @@
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="Basic UAS responder">
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv request="INVITE" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
SIP/2.0 401 Unauthorized
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
WWW-Authenticate: Digest realm="jambonz.org", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
Content-Length: 0
]]>
</send>
<recv request="ACK"
rtd="true"
crlf="true">
</recv>
<recv request="INVITE" crlf="true">
<action>
<ereg regexp=".*" search_in="hdr" header="Subject:" assign_to="1" />
<verifyauth assign_to="authvalid" username="foo" password="bar" />
</action>
</recv>
<nop hide="true" test="authvalid" next="goodauth" />
<send retrans="500">
<![CDATA[
SIP/2.0 403 Forbidden
[last_Via:]
[last_From:]
[last_To:];tag=[pid]SIPpTag01[call_number]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>;expires=60
Content-Type: application/sdp
Content-Length: 0
]]>
</send>
<recv request="ACK" next="endit"
rtd="true"
crlf="true">
</recv>
<label id="goodauth"/>
<send>
<![CDATA[
@@ -93,7 +139,7 @@
<recv request="BYE">
</recv>
<send next="2">
<send>
<![CDATA[
SIP/2.0 200 OK
@@ -108,23 +154,7 @@
]]>
</send>
<label id="1"/>
<send>
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
Content-Length: 0
]]>
</send>
<label id="2"/>
<label id="endit"/>
</scenario>
+34 -8
View File
@@ -1,13 +1,19 @@
const test = require('blue-tape');
const test = require('tape');
const { output, sippUac } = require('./sipp')('test_sbc-outbound');
const {execSync} = require('child_process');
const pwd = process.env.TRAVIS ? '' : '-p$MYSQL_ROOT_PASSWORD';
const debug = require('debug')('jambonz:sbc-outbound');
const consoleLogger = {error: console.error, info: console.log, debug: console.log};
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function waitFor(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms * 1000);
});
}
function connect(connectable) {
return new Promise((resolve, reject) => {
connectable.on('connect', () => {
@@ -18,11 +24,13 @@ function connect(connectable) {
test('sbc-outbound tests', async(t) => {
const {srf} = require('../app');
const { queryCdrs } = srf.locals;
try {
await connect(srf);
/* call to unregistered user */
debug('successfully connected to drachtio server');
await sippUac('uac-pcap-device-404.xml');
t.pass('return 404 to outbound attempt to unregistered user/device');
@@ -31,29 +39,47 @@ test('sbc-outbound tests', async(t) => {
t.pass('successfully completed outbound call to configured sip trunk');
// re-rack test data
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/jambones-sql.sql`);
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/populate-test-data2.sql`);
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-data2.sql`);
/* call to PSTN with lcr configured */
await sippUac('uac-pcap-carrier-success.xml');
t.pass('successfully completed outbound lcr carrier with crankback after failure');
// re-rack test data
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/jambones-sql.sql`);
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/populate-test-data3.sql`);
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-data3.sql`);
/* call to PSTN where caller hangs up during outdial */
await sippUac('uac-cancel.xml');
t.pass('successfully handled caller hangup during lcr outdial');
// re-rack test data
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/jambones-sql.sql`);
execSync(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/populate-test-data4.sql`);
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-data4.sql`);
/* reinvite after call established */
await sippUac('uac-pcap-carrier-success-reinvite.xml');
t.pass('successfully handled reinvite during lcr outdial');
/* invite to sipUri that challenges */
await sippUac('uac-sip-uri-auth-success.xml');
t.pass('successfully connected to sip uri that requires auth');
// 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`);
/* fails when session limit exceeded */
await sippUac('uac-pcap-carrier-fail-limits.xml');
t.pass('fails when max calls in progress');
await waitFor(10);
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
//console.log(`cdrs: ${JSON.stringify(res)}`);
t.ok(res.total === 6, 'wrote 6 cdrs');
srf.disconnect();
} catch (err) {
console.error(err);
+5 -5
View File
@@ -1,5 +1,5 @@
const { spawn } = require('child_process');
const debug = require('debug')('test:sipp');
const debug = require('debug')('jambonz:ci');
let network;
const obj = {};
let output = '';
@@ -28,7 +28,7 @@ obj.sippUac = (file, opts) => {
opts = opts || {};
const cmd = 'docker';
let args = [
'run', '-ti', '--rm', '--net', `${network}`
'run', '--rm', '--net', `${network}`
]
.concat(opts.ip ? ['--ip', opts.ip] : [])
.concat([
@@ -46,7 +46,7 @@ obj.sippUac = (file, opts) => {
'sbc'
]);
//console.log(`args: ${args}`);
debug(`args: ${args}`);
clearOutput();
return new Promise((resolve, reject) => {
@@ -67,8 +67,8 @@ obj.sippUac = (file, opts) => {
debug(`stdout: ${data}`);
addOutput(data.toString());
});
child_process.stdout.on('data', (data) => {
debug(`stdout: ${data}`);
child_process.stderr.on('data', (data) => {
debug(`stderr: ${data}`);
addOutput(data.toString());
});
});