mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 02:04:19 +00:00
Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff44b3fbc9 | ||
|
|
d2416c8ba4 | ||
|
|
574cdcb216 | ||
|
|
3e01cdeced | ||
|
|
05db83a0b3 | ||
|
|
5b801bea84 | ||
|
|
e8840c20a0 | ||
|
|
793346fac4 | ||
|
|
fd83adadd7 | ||
|
|
8a7cc9f3e8 | ||
|
|
bc76fc6495 | ||
|
|
0b115a803f | ||
|
|
25a95a5deb | ||
|
|
fc5c17848f | ||
|
|
3d2178897c | ||
|
|
fdf334ec61 | ||
|
|
da3193f45b | ||
|
|
f3564c57bb | ||
|
|
f580eea102 | ||
|
|
5e690b081a | ||
|
|
a7f5dc4f35 | ||
|
|
d842dd99b8 | ||
|
|
7d0930f6b2 | ||
|
|
30161b1da0 | ||
|
|
2b94a344b1 | ||
|
|
1da347c479 | ||
|
|
03d6591f8a | ||
|
|
180b33f803 | ||
|
|
4e5fbbd650 | ||
|
|
1026bcb9cd | ||
|
|
4956e13b9e | ||
|
|
f0cbd6aef5 | ||
|
|
708441a070 | ||
|
|
09688c9b9e | ||
|
|
68f6bfc1d3 | ||
|
|
68574635ee | ||
|
|
ce6ca6c21a | ||
|
|
f661883095 | ||
|
|
bd0c8d2783 | ||
|
|
a2c8abb632 | ||
|
|
b0d3e6bbac | ||
|
|
782102b6c1 | ||
|
|
25c1f11935 | ||
|
|
9826174ecf | ||
|
|
6bb0b21100 | ||
|
|
08f5f1f58e | ||
|
|
5d1909e50c | ||
|
|
4f947ad3f2 | ||
|
|
dccd57935e | ||
|
|
c66ba70500 | ||
|
|
e213409dec | ||
|
|
96906bd0ad | ||
|
|
cff42ef539 | ||
|
|
2d49b6ed8e | ||
|
|
160f888643 | ||
|
|
521e540901 | ||
|
|
d6bd607aee | ||
|
|
e4313fd5d3 |
+1
-1
@@ -8,7 +8,7 @@
|
||||
"jsx": false,
|
||||
"modules": false
|
||||
},
|
||||
"ecmaVersion": 2017
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"plugins": ["promise"],
|
||||
"rules": {
|
||||
|
||||
@@ -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
@@ -1,10 +0,0 @@
|
||||
sudo: required
|
||||
language: node_js
|
||||
node_js:
|
||||
- "lts/*"
|
||||
services:
|
||||
- docker
|
||||
- mysql
|
||||
script:
|
||||
- npm test
|
||||
|
||||
+9
-7
@@ -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,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
|
||||
|
||||
@@ -1,80 +1,76 @@
|
||||
# sbc-outbound [](http://travis-ci.org/jambonz/sbc-outbound)
|
||||
# sbc-outbound 
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@@ -14,19 +14,25 @@ const opts = Object.assign({
|
||||
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
||||
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
||||
const logger = require('pino')(opts);
|
||||
const StatsCollector = require('jambonz-stats-collector');
|
||||
const StatsCollector = require('@jambonz/stats-collector');
|
||||
const stats = srf.locals.stats = new StatsCollector(logger);
|
||||
const {route, setLogger} = require('./lib/middleware');
|
||||
const CallSession = require('./lib/call-session');
|
||||
const {performLcr} = require('jambonz-db-helpers')({
|
||||
const {performLcr, lookupAllTeamsFQDNs, lookupAccountBySipRealm} = 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};
|
||||
const {getRtpEngine} = require('jambonz-rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, {
|
||||
srf.locals.dbHelpers = {performLcr, lookupAllTeamsFQDNs, lookupAccountBySipRealm};
|
||||
const {createHash, retrieveHash} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
srf.locals.realtimeDbHelpers = {createHash, retrieveHash};
|
||||
|
||||
const {getRtpEngine} = require('@jambonz/rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, {
|
||||
emitter: srf.locals.stats
|
||||
});
|
||||
srf.locals.getRtpEngine = getRtpEngine;
|
||||
@@ -61,4 +67,7 @@ setInterval(() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:outbound']);
|
||||
}, 5000);
|
||||
|
||||
const {pingMsTeamsGateways} = require('./lib/utils');
|
||||
pingMsTeamsGateways(logger, srf);
|
||||
|
||||
module.exports = {srf};
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"DTLS": "off",
|
||||
"SDES": "off",
|
||||
"ICE": "remove",
|
||||
"flags": ["media handover"],
|
||||
"rtcp-mux": ["demux"]
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
{
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no"],
|
||||
"rtcp-mux": ["require"]
|
||||
"default": {
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["require"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
"ICE": "force",
|
||||
"flags": ["generate mid", "media handover",
|
||||
"SDES-no-AES_CM_128_HMAC_SHA1_32",
|
||||
"SDES-no-F8_128_HMAC_SHA1_80",
|
||||
"SDES-no-F8_128_HMAC_SHA1_32",
|
||||
"SDES-no-NULL_HMAC_SHA1_80",
|
||||
"SDES-no-NULL_HMAC_SHA1_32",
|
||||
"SDES-no-AES_192_CM_HMAC_SHA1_80",
|
||||
"SDES-no-AES_192_CM_HMAC_SHA1_32",
|
||||
"SDES-no-AES_256_CM_HMAC_SHA1_80",
|
||||
"SDES-no-AES_256_CM_HMAC_SHA1_32"
|
||||
],
|
||||
"rtcp-mux": ["require"]
|
||||
}
|
||||
}
|
||||
+208
-22
@@ -2,8 +2,29 @@ const Emitter = require('events');
|
||||
const {makeRtpEngineOpts} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {SipError} = require('drachtio-srf');
|
||||
const {parseUri, stringifyUri} = 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
|
||||
*/
|
||||
const createBLegFromHeader = (req, teams) => {
|
||||
const from = req.getParsedHeader('From');
|
||||
const host = teams ? req.get('X-MS-Teams-Tenant-FQDN') : 'localhost';
|
||||
const uri = parseUri(from.uri);
|
||||
if (uri && uri.user) return `sip:${uri.user}@${host}`;
|
||||
return `sip:anonymous@${host}`;
|
||||
};
|
||||
const createBLegToHeader = (req, teams) => {
|
||||
const to = req.getParsedHeader('To');
|
||||
const host = teams ? req.get('X-MS-Teams-Tenant-FQDN') : 'localhost';
|
||||
const uri = parseUri(to.uri);
|
||||
if (uri && uri.user) return `sip:${uri.user}@${host}`;
|
||||
return `sip:anonymous@${host}`;
|
||||
};
|
||||
|
||||
class CallSession extends Emitter {
|
||||
constructor(logger, req, res) {
|
||||
super();
|
||||
@@ -12,12 +33,13 @@ class CallSession extends Emitter {
|
||||
this.srf = req.srf;
|
||||
this.performLcr = this.srf.locals.dbHelpers.performLcr;
|
||||
this.logger = logger.child({callId: req.get('Call-ID')});
|
||||
this.useWss = req.locals.registration && req.locals.registration.protocol === 'wss';
|
||||
this.useWss = req.locals.registration && req.locals.registration.protocol === 'wss';
|
||||
this.stats = this.srf.locals.stats;
|
||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const teams = this.teams = this.req.locals.target === 'teams';
|
||||
const engine = this.srf.locals.getRtpEngine();
|
||||
if (!engine) {
|
||||
this.logger.info('No available rtpengines, rejecting call!');
|
||||
@@ -27,34 +49,74 @@ class CallSession extends Emitter {
|
||||
}
|
||||
debug(`got engine: ${JSON.stringify(engine)}`);
|
||||
const {offer, answer, del} = engine;
|
||||
const {createHash, retrieveHash} = this.srf.locals.realtimeDbHelpers;
|
||||
this.offer = offer;
|
||||
this.answer = answer;
|
||||
this.del = del;
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, this.useWss);
|
||||
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 = {
|
||||
'From': createBLegFromHeader(this.req, teams),
|
||||
'To': createBLegToHeader(this.req, teams),
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK'
|
||||
};
|
||||
|
||||
if (this.req.locals.registration) {
|
||||
debug(`sending call to user ${JSON.stringify(this.req.locals.registration)}`);
|
||||
proxy = this.req.locals.registration.contact;
|
||||
uris = [this.req.uri];
|
||||
debug(`sending call to registered user ${JSON.stringify(this.req.locals.registration)}`);
|
||||
const contact = this.req.locals.registration.contact;
|
||||
let destUri = this.req.uri;
|
||||
if (this.req.has('X-Override-To')) {
|
||||
const dest = this.req.get('X-Override-To');
|
||||
const uri = parseUri(contact);
|
||||
uri.user = dest;
|
||||
destUri = stringifyUri(uri);
|
||||
this.logger.info(`overriding destination user with ${dest}, so final uri is ${destUri}`);
|
||||
}
|
||||
if (contact.includes('transport=ws')) {
|
||||
uris = [contact];
|
||||
}
|
||||
else {
|
||||
proxy = this.req.locals.registration.proxy;
|
||||
uris = [destUri];
|
||||
}
|
||||
}
|
||||
else if (this.req.locals.target === 'forward') {
|
||||
uris = [this.req.uri];
|
||||
}
|
||||
else if (teams) {
|
||||
const vmailParam = 'opaque=app:voicemail';
|
||||
proxy = `sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com:5061;transport=tls`;
|
||||
if (this.req.uri.includes(vmailParam)) {
|
||||
uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com;${vmailParam}`];
|
||||
}
|
||||
else uris = [`sip:${this.req.calledNumber}@sip.pstnhub.microsoft.com`];
|
||||
Object.assign(headers, {
|
||||
Contact: `sip:${this.req.calledNumber}@${this.req.get('X-MS-Teams-Tenant-FQDN')}:5061;transport=tls`
|
||||
});
|
||||
}
|
||||
else {
|
||||
debug('calling lcr');
|
||||
try {
|
||||
// strip leading plus sign
|
||||
/**
|
||||
* We normalize the called number by removing a leading + before sending it to LCR..
|
||||
* but LCR will return us an array of sip uris, with leading + for carriers that require it
|
||||
*/
|
||||
const routableNumber = this.req.calledNumber.startsWith('+') ?
|
||||
this.req.calledNumber.slice(1) :
|
||||
this.req.calledNumber;
|
||||
uris = await this.performLcr(routableNumber);
|
||||
if (!uris || uris.length === 0) throw new Error('no routes found');
|
||||
//uris = await this.performLcr(routableNumber);
|
||||
const gateways = await this.performLcr(routableNumber);
|
||||
if (!gateways || gateways.length === 0) throw new Error('no routes found');
|
||||
debug(`got gateways: ${JSON.stringify(gateways)}`);
|
||||
gateways.forEach((gw) => mapGateways.set(gw.uri, gw.auth));
|
||||
uris = gateways.map((gw) => gw.uri);
|
||||
} catch (err) {
|
||||
debug(err);
|
||||
this.logger.error(err, 'Error performing lcr');
|
||||
@@ -67,8 +129,16 @@ class CallSession extends Emitter {
|
||||
|
||||
// rtpengine 'offer'
|
||||
debug('sending offer command to rtpengine');
|
||||
const response = await this.offer(this.rtpEngineOpts.offer);
|
||||
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: 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');
|
||||
@@ -77,20 +147,48 @@ class CallSession extends Emitter {
|
||||
// crank through the list of gateways until connected, exhausted or caller hangs up
|
||||
let earlyMedia = false;
|
||||
while (uris.length) {
|
||||
const hdrs = { ...headers};
|
||||
const uri = uris.shift();
|
||||
const auth = mapGateways.get(uri);
|
||||
const passFailure = 0 === uris.length; // only a single target
|
||||
if (0 === uris.length) {
|
||||
try {
|
||||
const key = makeInviteInProgressKey(this.req.get('Call-ID'));
|
||||
const obj = await retrieveHash(key);
|
||||
if (obj && obj.callId && obj.cseq) {
|
||||
Object.assign(hdrs, {
|
||||
'Call-ID': obj.callId,
|
||||
'CSeq': `${obj.cseq} INVITE`
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Error retrieving iip key');
|
||||
}
|
||||
}
|
||||
debug(`sending INVITE to ${uri} via ${proxy})`);
|
||||
this.logger.info(`sending INVITE to ${uri}`);
|
||||
try {
|
||||
const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, {
|
||||
proxy,
|
||||
passFailure: false,
|
||||
proxyRequestHeaders: ['all'],
|
||||
proxyResponseHeaders: ['all'],
|
||||
passFailure,
|
||||
proxyRequestHeaders: ['all', '-X-MS-Teams-FQDN', '-X-MS-Teams-Tenant-FQDN', 'X-CID', '-Allow',
|
||||
'-X-Account-Sid', '-Session-Expires', 'Min-SE'],
|
||||
proxyResponseHeaders: ['all', '-Allow', '-Session-Expires'],
|
||||
headers: hdrs,
|
||||
auth,
|
||||
localSdpB: response.sdp,
|
||||
localSdpA: async(sdp, res) => {
|
||||
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
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');
|
||||
@@ -98,6 +196,18 @@ class CallSession extends Emitter {
|
||||
return response.sdp;
|
||||
}
|
||||
}, {
|
||||
cbRequest: async(err, inv) => {
|
||||
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;
|
||||
}
|
||||
@@ -121,7 +231,7 @@ class CallSession extends Emitter {
|
||||
if (err instanceof SipError) this.logger.info(`final call failure ${err.status}`);
|
||||
else this.logger.error(err, 'unexpected call failure');
|
||||
debug(`got final outdial error: ${err}`);
|
||||
this.res.send(err.status || 500);
|
||||
if (!passFailure) this.res.send(err.status || 500);
|
||||
this.emit('failed');
|
||||
this.rtpEngineResource.destroy();
|
||||
const tags = ['accepted:no', `sipStatus:${err.status || 500}`];
|
||||
@@ -153,26 +263,49 @@ class CallSession extends Emitter {
|
||||
this.logger.info('call ended');
|
||||
this.rtpEngineResource.destroy();
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
dlg.other.destroy();
|
||||
});
|
||||
|
||||
//re-invite
|
||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||
});
|
||||
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
uac.on('modify', this._onReinvite.bind(this, uac));
|
||||
|
||||
uas.on('refer', this._onFeatureServerTransfer.bind(this, uas));
|
||||
|
||||
|
||||
// default forwarding of other request types
|
||||
forwardInDialogRequests(uas);
|
||||
forwardInDialogRequests(uac, ['info', 'notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
async _onReinvite(dlg, req, res) {
|
||||
try {
|
||||
let response = await this.offer(Object.assign({sdp: req.body}, this.rtpEngineOpts.offer));
|
||||
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,
|
||||
};
|
||||
|
||||
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({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
opts = {
|
||||
...this.rtpEngineOpts.common,
|
||||
...answerMedia,
|
||||
'from-tag': fromTag,
|
||||
'to-tag': toTag,
|
||||
sdp
|
||||
};
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
@@ -184,6 +317,59 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
async _onFeatureServerTransfer(dlg, req, res) {
|
||||
try {
|
||||
const referTo = req.getParsedHeader('Refer-To');
|
||||
const uri = parseUri(referTo.uri);
|
||||
this.logger.info({uri, referTo}, 'received REFER from feature server');
|
||||
const arr = /context-(.*)/.exec(uri.user);
|
||||
if (!arr) {
|
||||
this.logger.info(`invalid Refer-To header: ${referTo.uri}`);
|
||||
return res.send(501);
|
||||
}
|
||||
res.send(202);
|
||||
|
||||
// invite to new fs
|
||||
const headers = {};
|
||||
if (req.has('X-Retain-Call-Sid')) {
|
||||
Object.assign(headers, {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')});
|
||||
}
|
||||
const uas = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
|
||||
this.uas = uas;
|
||||
uas.other = this.uac;
|
||||
this.uac.other = uas;
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
uas.on('refer', this._onFeatureServerTransfer.bind(this, uas));
|
||||
uas.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy();
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
uas.other.destroy();
|
||||
});
|
||||
|
||||
// now we can destroy the old dialog
|
||||
dlg.destroy();
|
||||
|
||||
// modify rtpengine to stream to new feature server
|
||||
// NB: the media handover flag should suffice to tell rtpengine to relearn addresses
|
||||
/*
|
||||
let response = await this.offer(Object.assign(this.rtpEngineOpts.offer, {sdp: this.uas.remote.sdp}));
|
||||
if ('ok' !== response.result) {
|
||||
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
const sdp = await this.uas.other.modify(response.sdp);
|
||||
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
*/
|
||||
this.logger.info('successfully moved call to new feature server');
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling refer from feature server');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CallSession;
|
||||
|
||||
+23
-9
@@ -22,6 +22,7 @@ function route(opts) {
|
||||
const registrar = new Registrar(opts);
|
||||
return 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) {
|
||||
@@ -43,7 +44,12 @@ function route(opts) {
|
||||
let reg;
|
||||
const dotDecimalHost = /^[0-9\.]+$/.test(uri.host);
|
||||
|
||||
if (!dotDecimalHost) {
|
||||
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';
|
||||
return next();
|
||||
}
|
||||
else if (!dotDecimalHost) {
|
||||
// uri host is not a dot-decimal address, so try to look up user
|
||||
debug(`searching for registered user ${aor}`);
|
||||
reg = await registrar.query(aor);
|
||||
@@ -53,20 +59,28 @@ 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]}`);
|
||||
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';
|
||||
return next();
|
||||
}
|
||||
else {
|
||||
// if the sip domain is one of ours return 404
|
||||
const account = await lookupAccountBySipRealm(uri.host);
|
||||
if (account) {
|
||||
logger.info({host: uri.host, account}, `returning 404 to unregistered user in valid domain: ${req.uri}`);
|
||||
res.send(404);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!isLocalUri(uri.host, req) /*|| !dotDecimalHost */) {
|
||||
// TODO: need to try to forward calls to a DNS host name
|
||||
// so long as that domain name is not associated with an account or SP in our DB
|
||||
if (!dotDecimalHost || !isLocalUri(uri.host, req)) {
|
||||
// call that needs to be forwarded to a sip endpoint
|
||||
logger.info(`forwarding call to sip endpoint ${req.uri}`);
|
||||
req.locals.target = 'forward';
|
||||
return next();
|
||||
|
||||
+50
-6
@@ -2,14 +2,25 @@ const rtpCharacteristics = require('../data/rtp-transcoding');
|
||||
const srtpCharacteristics = require('../data/srtp-transcoding');
|
||||
const debug = require('debug')('jambonz:sbc-outbound');
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) {
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const from = req.getParsedHeader('from');
|
||||
const common = {'call-id': req.get('Call-ID'), 'from-tag': from.params.tag};
|
||||
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
|
||||
const dstOpts = dstIsUsingSrtp ? srtpOpts : rtpCharacteristics;
|
||||
const srctOpts = 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']}, common,
|
||||
dstIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics),
|
||||
answer: Object.assign({}, common, srcIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics)
|
||||
uas: {
|
||||
tag: from.params.tag,
|
||||
mediaOpts: srctOpts
|
||||
},
|
||||
uac: {
|
||||
tag: null,
|
||||
mediaOpts: dstOpts
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +38,40 @@ function selectHostPort(hostport, protocol) {
|
||||
return sel[0];
|
||||
}
|
||||
|
||||
function pingMs(logger, srf, gateway, fqdns) {
|
||||
const uri = `sip:${gateway}`;
|
||||
const proxy = `sip:${gateway}:5061;transport=tls`;
|
||||
fqdns.forEach((fqdn) => {
|
||||
const contact = `<sip:${fqdn}:5061;transport=tls>`;
|
||||
srf.request(uri, {
|
||||
method: 'OPTIONS',
|
||||
proxy,
|
||||
headers: {
|
||||
'Contact': contact,
|
||||
'From': contact,
|
||||
}
|
||||
}).catch((err) => logger.error(err, `Error pinging MS Teams at ${gateway}`));
|
||||
});
|
||||
}
|
||||
function pingMsTeamsGateways(logger, srf) {
|
||||
const {lookupAllTeamsFQDNs} = srf.locals.dbHelpers;
|
||||
lookupAllTeamsFQDNs()
|
||||
.then((fqdns) => {
|
||||
if (fqdns.length > 0) {
|
||||
['sip.pstnhub.microsoft.com', 'sip2.pstnhub.microsoft.com', 'sip3.pstnhub.microsoft.com']
|
||||
.forEach((gw) => {
|
||||
setInterval(pingMs.bind(this, logger, srf, gw, fqdns), 60000);
|
||||
});
|
||||
}
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err, 'Error looking up all ms teams fqdns');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeRtpEngineOpts,
|
||||
selectHostPort
|
||||
selectHostPort,
|
||||
pingMsTeamsGateways
|
||||
};
|
||||
|
||||
Generated
+5889
-3367
File diff suppressed because it is too large
Load Diff
+12
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-outbound",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.4",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
@@ -22,28 +22,26 @@
|
||||
"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 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 node test/ | ./node_modules/.bin/tap-spec",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"@jambonz/db-helpers": "^0.5.10",
|
||||
"@jambonz/realtimedb-helpers": "^0.2.22",
|
||||
"@jambonz/rtpengine-utils": "^0.1.8",
|
||||
"@jambonz/stats-collector": "^0.1.5",
|
||||
"debug": "^4.3.1",
|
||||
"drachtio-fn-b2b-sugar": "^0.0.12",
|
||||
"drachtio-srf": "^4.4.28",
|
||||
"jambonz-rtpengine-utils": "0.1.1",
|
||||
"jambonz-db-helpers": "^0.3.2",
|
||||
"jambonz-mw-registrar": "^0.1.2",
|
||||
"jambonz-stats-collector": "0.0.3",
|
||||
"pino": "^5.14.0",
|
||||
"rtpengine-client": "^0.0.8"
|
||||
"drachtio-srf": "^4.4.46",
|
||||
"jambonz-mw-registrar": "^0.1.3",
|
||||
"pino": "^6.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"blue-tape": "^1.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^14.1.1",
|
||||
"tap": "^14.10.2",
|
||||
"tap-dot": "^2.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"tap-spec": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const test = require('tape').test ;
|
||||
const test = require('blue-tape');
|
||||
//const test = require('tape').test ;
|
||||
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();
|
||||
@@ -11,7 +12,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();
|
||||
@@ -19,7 +20,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();
|
||||
|
||||
@@ -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@'%';
|
||||
|
||||
+201
-139
@@ -1,204 +1,266 @@
|
||||
/* SQLEditor (MySQL (2))*/
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS `call_routes`;
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_carrier_set_entry`;
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_routes`;
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `api_keys`;
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
||||
DROP TABLE IF EXISTS `phone_numbers`;
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS `sip_gateways`;
|
||||
DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS `voip_carriers`;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS `accounts`;
|
||||
DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS `applications`;
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS `service_providers`;
|
||||
DROP TABLE IF EXISTS voip_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS `webhooks`;
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
DROP TABLE IF EXISTS applications;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `call_routes`
|
||||
DROP TABLE IF EXISTS service_providers;
|
||||
|
||||
DROP TABLE IF EXISTS webhooks;
|
||||
|
||||
CREATE TABLE call_routes
|
||||
(
|
||||
`call_route_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`priority` INTEGER NOT NULL,
|
||||
`account_sid` CHAR(36) NOT NULL,
|
||||
`regex` VARCHAR(255) NOT NULL,
|
||||
`application_sid` CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (`call_route_sid`)
|
||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
priority INTEGER NOT NULL,
|
||||
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';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `lcr_routes`
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
`lcr_route_sid` CHAR(36),
|
||||
`regex` VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
`description` VARCHAR(1024),
|
||||
`priority` INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (`lcr_route_sid`)
|
||||
lcr_route_sid CHAR(36),
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `api_keys`
|
||||
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),
|
||||
PRIMARY KEY (`api_key_sid`)
|
||||
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 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';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `voip_carriers`
|
||||
CREATE TABLE ms_teams_tenants
|
||||
(
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`description` VARCHAR(255),
|
||||
`account_sid` CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
|
||||
`application_sid` CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
|
||||
PRIMARY KEY (`voip_carrier_sid`)
|
||||
ms_teams_tenant_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
application_sid CHAR(36),
|
||||
tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
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 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,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE voip_carriers
|
||||
(
|
||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
|
||||
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,
|
||||
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||
register_username VARCHAR(64),
|
||||
register_sip_realm VARCHAR(64),
|
||||
register_password VARCHAR(64),
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `phone_numbers`
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
`phone_number_sid` CHAR(36) UNIQUE ,
|
||||
`number` VARCHAR(32) NOT NULL UNIQUE ,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`account_sid` CHAR(36),
|
||||
`application_sid` CHAR(36),
|
||||
PRIMARY KEY (`phone_number_sid`)
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
PRIMARY KEY (phone_number_sid)
|
||||
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks`
|
||||
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`)
|
||||
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 IF NOT EXISTS `lcr_carrier_set_entry`
|
||||
CREATE TABLE sip_gateways
|
||||
(
|
||||
`lcr_carrier_set_entry_sid` CHAR(36),
|
||||
`workload` INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
`lcr_route_sid` CHAR(36) NOT NULL,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`priority` INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (`lcr_carrier_set_entry_sid`)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sip_gateways`
|
||||
(
|
||||
`sip_gateway_sid` CHAR(36),
|
||||
`ipv4` VARCHAR(32) 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',
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`sip_gateway_sid`)
|
||||
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.',
|
||||
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',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `applications`
|
||||
CREATE TABLE lcr_carrier_set_entry
|
||||
(
|
||||
`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',
|
||||
`call_status_hook_sid` CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
`speech_synthesis_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_synthesis_voice` VARCHAR(64) NOT NULL DEFAULT 'en-US-Wavenet-C',
|
||||
`speech_recognizer_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_recognizer_language` VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
PRIMARY KEY (`application_sid`)
|
||||
lcr_carrier_set_entry_sid CHAR(36),
|
||||
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
lcr_route_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
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 ',
|
||||
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',
|
||||
PRIMARY KEY (application_sid)
|
||||
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `service_providers`
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`description` VARCHAR(255),
|
||||
`root_domain` VARCHAR(128) UNIQUE ,
|
||||
`registration_hook_sid` CHAR(36),
|
||||
PRIMARY KEY (`service_provider_sid`)
|
||||
service_provider_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
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';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `accounts`
|
||||
CREATE TABLE accounts
|
||||
(
|
||||
`account_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`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',
|
||||
`device_calling_application_sid` CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (`account_sid`)
|
||||
account_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
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',
|
||||
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (account_sid)
|
||||
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
CREATE INDEX `call_routes_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`);
|
||||
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 application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `api_keys_api_key_sid_idx` ON `api_keys` (`api_key_sid`);
|
||||
CREATE INDEX `api_keys_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`);
|
||||
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);
|
||||
|
||||
CREATE INDEX `api_keys_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 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 `voip_carriers_voip_carrier_sid_idx` ON `voip_carriers` (`voip_carrier_sid`);
|
||||
CREATE INDEX `voip_carriers_name_idx` ON `voip_carriers` (`name`);
|
||||
ALTER TABLE `voip_carriers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_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 `voip_carriers` ADD FOREIGN KEY application_sid_idxfk_1 (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX `phone_numbers_phone_number_sid_idx` ON `phone_numbers` (`phone_number_sid`);
|
||||
CREATE INDEX `phone_numbers_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`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
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);
|
||||
|
||||
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY lcr_route_sid_idxfk (`lcr_route_sid`) REFERENCES `lcr_routes` (`lcr_route_sid`);
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX name_idx ON users (name);
|
||||
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);
|
||||
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE UNIQUE INDEX `sip_gateways_sip_gateway_idx_hostport` ON `sip_gateways` (`ipv4`,`port`);
|
||||
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);
|
||||
|
||||
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (`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 UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
|
||||
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
|
||||
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk_4 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_hook_sid_idxfk (`call_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_status_hook_sid_idxfk (`call_status_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
|
||||
|
||||
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
|
||||
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
|
||||
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
|
||||
ALTER TABLE `service_providers` ADD FOREIGN KEY registration_hook_sid_idxfk (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE INDEX `accounts_account_sid_idx` ON `accounts` (`account_sid`);
|
||||
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
|
||||
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
|
||||
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY registration_hook_sid_idxfk_1 (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
CREATE INDEX application_sid_idx ON applications (application_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 `accounts` ADD FOREIGN KEY device_calling_application_sid_idxfk (`device_calling_application_sid`) REFERENCES `applications` (`application_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);
|
||||
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
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 registration_hook_sid_idxfk_1 (registration_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;
|
||||
|
||||
@@ -6,6 +6,7 @@ insert into accounts(account_sid, service_provider_sid, name, sip_realm, registr
|
||||
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||
|
||||
-- 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, e164_leading_plus, requires_register, register_username, register_sip_realm, register_password)
|
||||
values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', 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);
|
||||
|
||||
@@ -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
|
||||
@@ -27,6 +44,7 @@ services:
|
||||
|
||||
rtpengine:
|
||||
image: drachtio/rtpengine:latest
|
||||
command: rtpengine --interface private/172.39.0.12 --interface public/172.39.0.12!172.39.0.12
|
||||
ports:
|
||||
- "12222:22222/udp"
|
||||
networks:
|
||||
@@ -88,3 +106,13 @@ 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
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const test = require('tape').test ;
|
||||
const test = require('blue-tape');
|
||||
//const test = require('tape').test ;
|
||||
const exec = require('child_process').exec ;
|
||||
const async = require('async');
|
||||
|
||||
test('starting docker network..', (t) => {
|
||||
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
|
||||
t.end(err);
|
||||
t.pass('docker started');
|
||||
t.end(err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+2
-11
@@ -1,16 +1,7 @@
|
||||
const test = require('tape').test ;
|
||||
const test = require('blue-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,5 +1,4 @@
|
||||
require('./docker_start');
|
||||
require('./create-test-db');
|
||||
require('./sip-tests');
|
||||
require('./remove-test-db');
|
||||
require('./docker_stop');
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
const test = require('tape').test ;
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -25,10 +25,10 @@
|
||||
<send retrans="500" start_txn="invite">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:16173333456@jambonz.org SIP/2.0
|
||||
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@jambonz.org>
|
||||
To: <sip:16173333456@127.0.0.1>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:16173333456@jambonz.org SIP/2.0
|
||||
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@jambonz.org>
|
||||
To: <sip:16173333456@127.0.0.1>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
@@ -43,10 +43,10 @@
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:sip:+16173333456@jambonz.org SIP/2.0
|
||||
ACK sip:sip:+16173333456@127.0.0.1 SIP/2.0
|
||||
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:sip:+16173333456@jambonz.org>[peer_tag_param]
|
||||
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Subject: uac-pcap-carrier-success
|
||||
@@ -122,10 +122,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
BYE sip:sip:+16173333456@jambonz.org SIP/2.0
|
||||
BYE sip:sip:+16173333456@127.0.0.1 SIP/2.0
|
||||
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:sip:+16173333456@jambonz.org>[peer_tag_param]
|
||||
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 3 BYE
|
||||
Max-Forwards: 70
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:16173333456@jambonz.org SIP/2.0
|
||||
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@jambonz.org>
|
||||
To: <sip:16173333456@127.0.0.1>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
@@ -43,10 +43,10 @@
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:sip:+16173333456@jambonz.org SIP/2.0
|
||||
ACK sip:sip:+16173333456@127.0.0.1 SIP/2.0
|
||||
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:sip:+16173333456@jambonz.org>[peer_tag_param]
|
||||
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Subject: uac-pcap-carrier-success
|
||||
@@ -69,10 +69,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
BYE sip:sip:+16173333456@jambonz.org SIP/2.0
|
||||
BYE sip:sip:+16173333456@127.0.0.1 SIP/2.0
|
||||
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:sip:+16173333456@jambonz.org>[peer_tag_param]
|
||||
To: <sip:sip:+16173333456@127.0.0.1>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 2 BYE
|
||||
Max-Forwards: 70
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:jane@jambonz.org SIP/2.0
|
||||
INVITE sip:jane@sip.example.com 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:jane@jambonz.org>
|
||||
To: <sip:jane@sip.example.com>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
@@ -62,10 +62,10 @@
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:sip:jane@jambonz.org SIP/2.0
|
||||
ACK sip:sip:jane@sip.example.com SIP/2.0
|
||||
[last_Via]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:sip:jane@jambonz.org>[peer_tag_param]
|
||||
To: <sip:sip:jane@sip.example.com>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Subject: uac-pcap-device-success
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<?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
|
||||
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
|
||||
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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+12
-21
@@ -1,8 +1,8 @@
|
||||
const test = require('blue-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 pwd = '-p$MYSQL_ROOT_PASSWORD';
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
@@ -21,22 +21,9 @@ test('sbc-outbound tests', async(t) => {
|
||||
|
||||
try {
|
||||
await connect(srf);
|
||||
|
||||
// for now, the docker image for sbc-registrar needs a mysql database..
|
||||
// TODO: figure out how to add that into the docker network and build the schema there
|
||||
/* send a registration from 172.39.0.31 */
|
||||
//await sippUac('uac-register-auth-success.xml', {ip: '172.39.0.31', data_file: 'good_user.csv'});
|
||||
//t.pass('sip user/device registered over udp');
|
||||
|
||||
/* now start a sipp service waiting on 172.39.0.31 for an INVITE */
|
||||
//const p = sippUac('uas.xml', {ip: '172.39.0.31'});
|
||||
|
||||
/* now send an outbound call from the appserver targeted to the user that registered from 172.39.0.31 */
|
||||
//await sippUac('uac-pcap-device-success.xml');
|
||||
//await p;
|
||||
//t.pass('successfully completed outbound call to the registered user/devices');
|
||||
|
||||
/* 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');
|
||||
|
||||
@@ -45,29 +32,33 @@ 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');
|
||||
|
||||
srf.disconnect();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
+5
-5
@@ -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());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user