revamp to mysql for gateway configuration and major code refactor

This commit is contained in:
Dave Horton
2019-12-13 10:20:27 -05:00
parent c22b6f9c08
commit 5a529b2bb9
23 changed files with 1152 additions and 187 deletions

View File

@@ -4,6 +4,6 @@ node_js:
- "lts/*"
services:
- docker
- mysql
script:
- npm test

111
README.md
View File

@@ -32,84 +32,77 @@ the `rtpengine` object specifies the location of the rtpengine, which will typic
"level": "info"
}
```
##### authentication web callback
##### application server location
The sip trunk routing to internal application servers are specified as an array of IP addresses.
```
"authCallback": {
"uri": "http://example.com/auth",
"auth": {
"username": "foo",
"password": "bar"
}
"trunks": {
"appserver": ["sip:10.10.120.1"]
}
```
##### 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"]
}
}
```
the `authCallback` object specifies the http(s) url that a POST request will be sent to for each incoming REGISTER request. The body of the POST will be a json payload including the following information:
## Authentication
Authenticating users is the responsibility of the client by exposing an http callback. A POST request will be sent to the configured callback (i.e. the value in the `accounts.registration_hook` column in the associated sip realm value in the REGISTER request). The body of the POST will be a json payload including the following information:
```
{
"method": "REGISTER",
"username": "daveh",
"realm": "drachtio.org",
"nonce": "2q4gct3g3ghbfj34h3",
"uri": "sip:dhorton@drachtio.org",
"response": "djaduys9g9d",
}
{
"method": "REGISTER",
"expires": 3600,
"scheme": "digest",
"username": "john",
"realm": "jambonz.org",
"nonce": "157590482938000",
"uri": "sip:172.37.0.10:5060",
"response": "be641cf7951ff23ab04c57907d59f37d",
"qop": "auth",
"nc": "00000001",
"cnonce": "6b8b4567",
"algorithm": "MD5"
}
```
It is the responsibility of the customer-side logic to retrieve the associated password for the given username and authenticate the request by calculating a response token (per the algorithm described in [RFC 2617](https://tools.ietf.org/html/rfc2617#section-3.2.2)) and comparing it to that provided in the request.
It is the responsibility of the customer-side logic to retrieve the associated password for the given username and to then authenticate the request by calculating a response hash value (per the algorithm described in [RFC 2617](https://tools.ietf.org/html/rfc2617#section-3.2.2)) and comparing it to the response property in the http body.
The `auth` property in the `authCallback` object is optional. It should be provided if the customer callback is using HTTP Basic Authentication to protect the endpoint.
For example code showing how to calculate the response hash given the above inputs, [see here](https://github.com/jambonz/customer-auth-server/blob/master/lib/utils.js).
If the request is successfully authenticated, the callback should return a 200 OK response with a JSON body including:
For a simple, full-fledged example server doing the same, [see here](https://github.com/jambonz/customer-auth-server).
The customer server SHOULD return a 200 OK response to the http request in all cases with a json body indicating whether the request was successfully authenticated.
The body MUST include a `status` field with a value of either `ok` or `fail`, indicating whether the request was authenticated or not.
```
{"status": "ok"}
```
This will signal the application to accept the registration request, respond accordingly to the client, and update the redis database with the active registration.
In the case of failure, the customer-side application *should* return a 'msg' property indicating the reason, e.g.
Additionally, in the case of failure, the body MAY include a `msg` field with a human-readable description of why the authentication failed.
```
{"status": "fail", "msg": "invalid username"}
```
##### sip trunks
Inbound sip trunks are configured by specifing name and associated ip addresses. Additionally, the sip trunk for internal jambonz application servers is specified as an array of IP addresses.
```
"trunks": {
"inbound": [
{
"name": "carrier1",
"host": ["10.123.22.3"]
}
],
"appserver": ["sip:10.10.120.1"]
}
```
##### 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"]
}
}
```
## Forwarding behavior
This application acts as a back-to-back user agent and media proxy. When sending INVITEs on to the jambonz application servers, it adds the following headers onto the INVITE:
- `X-Forwarded-For`: the IP address of the client that sent the INVITE
- `X-Forwarded-Proto`: the transport protocol used by the client
- `X-Forwarded-Carrier`: the name of the inbound carrier, if applicable
## Tests
The automated test suite requires a docker installation.
#### 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.
```
npm test
MYSQL_ROOT_PASSWORD=foobar npm test
```

25
app.js
View File

@@ -2,7 +2,16 @@ const Srf = require('drachtio-srf');
const srf = new Srf();
const config = require('config');
const logger = require('pino')(config.get('logging'));
const {auth} = require('./lib/middleware');
const {
lookupAuthHook,
lookupSipGatewayBySignalingAddress
} = require('jambonz-db-helpers')(config.get('mysql'), logger);
srf.locals.dbHelpers = {
lookupAuthHook,
lookupSipGatewayBySignalingAddress
};
const {challengeDeviceCalls} = require('./lib/middleware')(srf, logger);
const CallSession = require('./lib/call-session');
// disable logging in test mode
if (process.env.NODE_ENV === 'test') {
@@ -21,8 +30,18 @@ if (config.has('drachtio.host')) {
else {
srf.listen(config.get('drachtio'));
}
if (process.env.NODE_ENV === 'test') {
srf.on('error', (err) => {
logger.info(err, 'Error connecting to drachtio');
});
}
srf.use('invite', auth);
srf.invite(require('./lib/invite')({log: logger}));
// challenge calls from devices, let calls from sip gateways through
srf.use('invite', [challengeDeviceCalls]);
srf.invite((req, res) => {
const session = new CallSession(logger, req, res);
session.connect();
});
module.exports = {srf};

View File

@@ -10,21 +10,14 @@
"logging": {
"level": "info"
},
"authCallback": {
"uri": "http://example.com/auth",
"auth": {
"user": "foo",
"pass": "bar"
}
"mysql": {
"host": "localhost",
"user": "jambones",
"password": "jambones",
"database": "jambones"
},
"trunks": {
"inbound": [
{
"name": "carrier1",
"host": ["10.123.22.3"]
}
],
"appserver": ["sip:10.10.120.1"]
"appserver": ["sip:172.38.0.11"]
},
"transcoding": {
"rtpCharacteristics" : {

View File

@@ -11,21 +11,14 @@
"logging": {
"level": "debug"
},
"authCallback": {
"uri": "http://127.0.0.1:4000/auth",
"auth": {
"user": "foo",
"pass ": "bar"
}
"mysql": {
"host": "localhost",
"user": "jambones_test",
"password": "jambones_test",
"database": "jambones_test"
},
"trunks": {
"inbound": [
{
"name": "carrier1",
"host": ["172.38.0.20"]
}
],
"appserver": ["sip:172.38.0.11"]
"appserver": ["172.38.0.11"]
},
"transcoding": {
"rtpCharacteristics" : {

131
lib/call-session.js Normal file
View File

@@ -0,0 +1,131 @@
const Emitter = require('events');
const config = require('config');
const Client = require('rtpengine-client').Client ;
const rtpengine = new Client();
const offer = rtpengine.offer.bind(rtpengine, config.get('rtpengine'));
const answer = rtpengine.answer.bind(rtpengine, config.get('rtpengine'));
const del = rtpengine.delete.bind(rtpengine, config.get('rtpengine'));
const {getAppserver, isWSS, makeRtpEngineOpts} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
const {parseUri, SipError} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-inbound');
class CallSession extends Emitter {
constructor(logger, req, res) {
super();
this.req = req;
this.res = res;
this.srf = req.srf;
this.logger = logger.child({callId: req.get('Call-ID')});
}
async connect() {
this.rtpEngineOpts = makeRtpEngineOpts(this.req, isWSS(this.req), false);
this.rtpEngineResource = {destroy: del.bind(rtpengine, this.rtpEngineOpts.common)};
const obj = parseUri(this.req.uri);
const appServer = getAppserver();
let proxy, host, uri;
// replace host part of uri if its an ipv4 address, leave it otherwise
if (/\d{1-3}\.\d{1-3}\.\d{1-3}\.\d{1-3}/.test(obj.host)) {
host = obj.host;
proxy = appServer;
}
else {
host = appServer;
}
if (obj.user) uri = `${obj.scheme}:${obj.user}@${host}`;
else uri = `${obj.scheme}:${host}`;
debug(`uri will be: ${uri}, proxy ${proxy}`);
try {
// rtpengine 'offer'
debug('sending offer command to rtpengine');
const response = await offer(this.rtpEngineOpts.offer);
debug(`response from rtpengine to offer ${JSON.stringify(response)}`);
if ('ok' !== response.result) {
this.logger.error(`rtpengine offer failed with ${JSON.stringify(response)}`);
throw new Error('rtpengine failed: answer');
}
// now send the INVITE in towards the feature servers
const headers = {'X-Forwarded-For': this.req.source_address};
if (this.req.locals.carrier) Object.assign(headers, {'X-Originating-Carrier': this.req.locals.carrier});
debug(`sending INVITE to ${proxy} with ${uri}`);
const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, {
proxy,
headers,
proxyRequestHeaders: ['all'],
proxyResponseHeaders: ['all'],
localSdpB: response.sdp,
localSdpA: async(sdp, res) => {
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
this.rtpEngineOpts.answer);
const response = await answer(opts);
if ('ok' !== response.result) {
this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`);
throw new Error('rtpengine failed: answer');
}
return response.sdp;
}
});
// successfully connected
this.logger.info('call connected');
debug('call connected');
this.emit('connected');
this._setHandlers({uas, uac});
return;
} catch (err) {
if (err instanceof SipError) {
this.logger.info(`call failed with ${err.status}`);
this.emit('failed');
this.rtpEngineResource.destroy();
}
}
}
_setHandlers({uas, uac}) {
this.uas = uas;
this.uac = uac;
[uas, uac].forEach((dlg) => {
//hangup
dlg.on('destroy', () => {
this.logger.info('call ended');
this.rtpEngineResource.destroy();
});
//re-invite
dlg.on('modify', this._onReinvite.bind(this, dlg));
});
// default forwarding of other request types
forwardInDialogRequests(uas);
}
async _onReinvite(dlg, req, res) {
try {
let response = await offer(Object.assign({sdp: req.body}, this.rtpEngineOpts.offer));
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);
response = await answer(opts);
if ('ok' !== response.result) {
res.send(488);
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
}
res.send(200, {body: response.sdp});
} catch (err) {
this.logger.error(err, 'Error handling reinvite');
}
}
}
module.exports = CallSession;

View File

@@ -1,62 +0,0 @@
const config = require('config');
const Client = require('rtpengine-client').Client ;
const rtpengine = new Client();
const offer = rtpengine.offer.bind(rtpengine, config.get('rtpengine'));
const answer = rtpengine.answer.bind(rtpengine, config.get('rtpengine'));
const del = rtpengine.delete.bind(rtpengine, config.get('rtpengine'));
const {getAppserver, isWSS, makeRtpEngineOpts} = require('./utils');
module.exports = handler;
function handler({log}) {
return async(req, res) => {
const logger = log.child({callId: req.get('Call-ID')});
const srf = req.srf;
const rtpEngineOpts = makeRtpEngineOpts(req, isWSS(req), false);
const rtpEngineResource = {destroy: del.bind(rtpengine, rtpEngineOpts.common)};
const uri = getAppserver();
logger.info(`received inbound INVITE from ${req.protocol}/${req.source_address}:${req.source_port}`);
try {
const response = await offer(rtpEngineOpts.offer);
if ('ok' !== response.result) {
res.send(480);
throw new Error(`failed allocating rtpengine endpoint: ${JSON.stringify(response)}`);
}
const {uas, uac} = await srf.createB2BUA(req, res, uri, {
headers: {
'X-Forwarded-For': req.source_address,
'X-Forwarded-Proto': req.getParsedHeader('Via')[0].protocol.toLowerCase(),
'X-Forwarded-Carrier': req.carrier_name
},
proxyRequestHeaders: ['User-Agent', 'Subject'],
localSdpB: response.sdp,
localSdpA: (sdp, res) => {
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
rtpEngineOpts.answer);
return answer(opts)
.then((response) => {
if ('ok' !== response.result) throw new Error('error allocating rtpengine');
return response.sdp;
});
}
});
logger.info('call connected');
setHandlers(logger, uas, uac, rtpEngineResource);
} catch (err) {
logger.error(err, 'Error connecting call');
rtpEngineResource.destroy();
}
};
}
function setHandlers(logger, uas, uac, rtpEngineResource) {
[uas, uac].forEach((dlg) => dlg.on('destroy', () => {
logger.info('call ended');
dlg.other.destroy();
rtpEngineResource.destroy();
}));
//TODO: handle re-INVITEs, REFER, INFO
}

View File

@@ -1,12 +1,28 @@
const {fromInboundTrunk} = require('./utils');
const config = require('config');
const authenticator = require('drachtio-http-authenticator')(config.get('authCallback'));
const debug = require('debug')('jambonz:sbc-inbound');
function auth(req, res, next) {
if (fromInboundTrunk(req)) {
return next();
module.exports = function(srf, logger) {
const {lookupSipGatewayBySignalingAddress, lookupAuthHook} = srf.locals.dbHelpers;
const authenticator = require('drachtio-http-authenticator')(lookupAuthHook, logger);
async function challengeDeviceCalls(req, res, next) {
req.locals = req.locals || {};
try {
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
if (!gateway) {
req.locals.originator = 'device';
return authenticator(req, res, next);
}
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
req.locals.originator = 'trunk';
req.locals.carrier = gateway.name;
next();
} catch (err) {
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
res.send(500);
}
}
authenticator(req, res, next);
}
module.exports = { auth };
return {
challengeDeviceCalls
};
};

View File

@@ -1,16 +1,6 @@
const config = require('config');
let idx = 0;
function fromInboundTrunk(req) {
const trunks = config.has('trunks.inbound') ?
config.get('trunks.inbound') : [];
if (isWSS(req)) return false;
const trunk = trunks.find((t) => t.host.includes(req.source_address));
if (!trunk) return false;
req.carrier_name = trunk.name;
return true;
}
function isWSS(req) {
return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws');
}
@@ -34,7 +24,6 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) {
}
module.exports = {
fromInboundTrunk,
isWSS,
getAppserver,
makeRtpEngineOpts

View File

@@ -18,20 +18,24 @@
"jslint": "eslint app.js lib"
},
"dependencies": {
"config": "^3.2.2",
"drachtio-http-authenticator": "0.0.5",
"drachtio-srf": "^4.4.14",
"pino": "^5.13.2",
"rtpengine-client": "0.0.8"
"config": "^3.2.4",
"debug": "^4.1.1",
"drachtio-fn-b2b-sugar": "0.0.12",
"drachtio-http-authenticator": "^0.1.7",
"drachtio-srf": "^4.4.25",
"jambonz-db-helpers": "^0.1.2",
"pino": "^5.14.0",
"rtpengine-client": "^0.0.8"
},
"devDependencies": {
"blue-tape": "^1.0.0",
"clear-module": "^4.0.0",
"eslint": "^6.1.0",
"eslint": "^6.7.2",
"eslint-plugin-promise": "^4.2.1",
"nyc": "^14.1.1",
"tap": "^14.6.1",
"tap": "^14.10.2",
"tap-dot": "^2.0.0",
"tap-spec": "^5.0.0"
"tap-spec": "^5.0.0",
"tape": "^4.11.0"
}
}

27
test/create-test-db.js Normal file
View File

@@ -0,0 +1,27 @@
const test = require('tape').test ;
const exec = require('child_process').exec ;
const pwd = process.env.TRAVIS ? '' : '-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) => {
if (err) return t.end(err);
t.pass('database successfully created');
t.end();
});
});
test('creating schema', (t) => {
exec(`mysql -h localhost -u root ${pwd} -D jambones_test < ${__dirname}/db/jambones-sql.sql`, (err, stdout, stderr) => {
if (err) return t.end(err);
t.pass('schema successfully created');
t.end();
});
});
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) => {
if (err) return t.end(err);
t.pass('test data set created');
t.end();
});
});

View File

@@ -0,0 +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;

272
test/db/jambones-sql.sql Normal file
View File

@@ -0,0 +1,272 @@
/* SQLEditor (MySQL (2))*/
DROP TABLE IF EXISTS `call_routes`;
DROP TABLE IF EXISTS `conference_participants`;
DROP TABLE IF EXISTS `queue_members`;
DROP TABLE IF EXISTS `calls`;
DROP TABLE IF EXISTS `phone_numbers`;
DROP TABLE IF EXISTS `applications`;
DROP TABLE IF EXISTS `conferences`;
DROP TABLE IF EXISTS `queues`;
DROP TABLE IF EXISTS `subscriptions`;
DROP TABLE IF EXISTS `registrations`;
DROP TABLE IF EXISTS `api_keys`;
DROP TABLE IF EXISTS `accounts`;
DROP TABLE IF EXISTS `service_providers`;
DROP TABLE IF EXISTS `sip_gateways`;
DROP TABLE IF EXISTS `voip_carriers`;
CREATE TABLE IF NOT EXISTS `applications`
(
`application_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`account_sid` CHAR(36) NOT NULL,
`call_hook` VARCHAR(255) NOT NULL,
`call_status_hook` VARCHAR(255) NOT NULL,
PRIMARY KEY (`application_sid`)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls with';
CREATE TABLE IF NOT EXISTS `call_routes`
(
`call_route_sid` CHAR(36) NOT NULL UNIQUE ,
`order` 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;
CREATE TABLE IF NOT EXISTS `conferences`
(
`id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE ,
`conference_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255),
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='An audio conference';
CREATE TABLE IF NOT EXISTS `conference_participants`
(
`conference_participant_sid` CHAR(36) NOT NULL UNIQUE ,
`call_sid` CHAR(36),
`conference_sid` CHAR(36) NOT NULL,
PRIMARY KEY (`conference_participant_sid`)
) ENGINE=InnoDB COMMENT='A relationship between a call and a conference that it is co';
CREATE TABLE IF NOT EXISTS `queues`
(
`id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE ,
`queue_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255),
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='A set of behaviors to be applied to parked calls';
CREATE TABLE IF NOT EXISTS `registrations`
(
`registration_sid` CHAR(36) NOT NULL UNIQUE ,
`username` VARCHAR(255) NOT NULL,
`domain` VARCHAR(255) NOT NULL,
`sip_contact` VARCHAR(255) NOT NULL,
`sip_user_agent` VARCHAR(255),
PRIMARY KEY (`registration_sid`)
) ENGINE=InnoDB COMMENT='An active sip registration';
CREATE TABLE IF NOT EXISTS `queue_members`
(
`queue_member_sid` CHAR(36) NOT NULL UNIQUE ,
`call_sid` CHAR(36),
`queue_sid` CHAR(36) NOT NULL,
`position` INTEGER,
PRIMARY KEY (`queue_member_sid`)
) ENGINE=InnoDB COMMENT='A relationship between a call and a queue that it is waiting';
CREATE TABLE IF NOT EXISTS `calls`
(
`call_sid` CHAR(36) NOT NULL UNIQUE ,
`parent_call_sid` CHAR(36),
`application_sid` CHAR(36),
`status_url` VARCHAR(255),
`time_start` DATETIME NOT NULL,
`time_alerting` DATETIME,
`time_answered` DATETIME,
`time_ended` DATETIME,
`direction` ENUM('inbound','outbound'),
`phone_number_sid` CHAR(36),
`inbound_user_sid` CHAR(36),
`outbound_user_sid` CHAR(36),
`calling_number` VARCHAR(255),
`called_number` VARCHAR(255),
`caller_name` VARCHAR(255),
`status` VARCHAR(255) NOT NULL COMMENT 'Possible values are queued, ringing, in-progress, completed, failed, busy and no-answer',
`sip_uri` VARCHAR(255) NOT NULL,
`sip_call_id` VARCHAR(255) NOT NULL,
`sip_cseq` INTEGER NOT NULL,
`sip_from_tag` VARCHAR(255) NOT NULL,
`sip_via_branch` VARCHAR(255) NOT NULL,
`sip_contact` VARCHAR(255),
`sip_final_status` INTEGER UNSIGNED,
`sdp_offer` VARCHAR(4096),
`sdp_answer` VARCHAR(4096),
`source_address` VARCHAR(255) NOT NULL,
`source_port` INTEGER UNSIGNED NOT NULL,
`dest_address` VARCHAR(255),
`dest_port` INTEGER UNSIGNED,
`url` VARCHAR(255),
PRIMARY KEY (`call_sid`)
) ENGINE=InnoDB COMMENT='A phone call';
CREATE TABLE IF NOT EXISTS `service_providers`
(
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL UNIQUE ,
`description` VARCHAR(255),
`root_domain` VARCHAR(255) UNIQUE ,
`registration_hook` VARCHAR(255),
`hook_basic_auth_user` VARCHAR(255),
`hook_basic_auth_password` VARCHAR(255),
PRIMARY KEY (`service_provider_sid`)
) ENGINE=InnoDB COMMENT='An organization that provides communication services to its ';
CREATE TABLE IF NOT EXISTS `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`)
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE IF NOT EXISTS `accounts`
(
`account_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL,
`sip_realm` VARCHAR(255) UNIQUE ,
`service_provider_sid` CHAR(36) NOT NULL,
`registration_hook` VARCHAR(255),
`hook_basic_auth_user` VARCHAR(255),
`hook_basic_auth_password` VARCHAR(255),
`is_active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`account_sid`)
) ENGINE=InnoDB COMMENT='A single end-user of the platform';
CREATE TABLE IF NOT EXISTS `subscriptions`
(
`id` INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE ,
`subscription_sid` CHAR(36) NOT NULL UNIQUE ,
`registration_sid` CHAR(36) NOT NULL,
`event` VARCHAR(255),
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='An active sip subscription';
CREATE TABLE IF NOT EXISTS `voip_carriers`
(
`voip_carrier_sid` CHAR(36) NOT NULL UNIQUE ,
`name` VARCHAR(255) NOT NULL UNIQUE ,
`description` VARCHAR(255),
PRIMARY KEY (`voip_carrier_sid`)
) ENGINE=InnoDB COMMENT='An external organization that can provide sip trunking and D';
CREATE TABLE IF NOT EXISTS `phone_numbers`
(
`phone_number_sid` CHAR(36) UNIQUE ,
`number` VARCHAR(255) 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 `sip_gateways`
(
`sip_gateway_sid` CHAR(36),
`ipv4` VARCHAR(32) NOT NULL,
`port` INTEGER NOT NULL DEFAULT 5060,
`inbound` BOOLEAN NOT NULL,
`outbound` BOOLEAN NOT NULL,
`voip_carrier_sid` CHAR(36) NOT NULL,
`is_active` BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (`sip_gateway_sid`)
);
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
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 (`account_sid`) REFERENCES `accounts` (`account_sid`);
CREATE INDEX `call_routes_call_route_sid_idx` ON `call_routes` (`call_route_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `call_routes` ADD FOREIGN KEY application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
CREATE INDEX `conferences_conference_sid_idx` ON `conferences` (`conference_sid`);
CREATE INDEX `conference_participants_conference_participant_sid_idx` ON `conference_participants` (`conference_participant_sid`);
ALTER TABLE `conference_participants` ADD FOREIGN KEY call_sid_idxfk (`call_sid`) REFERENCES `calls` (`call_sid`);
ALTER TABLE `conference_participants` ADD FOREIGN KEY conference_sid_idxfk (`conference_sid`) REFERENCES `conferences` (`conference_sid`);
CREATE INDEX `queues_queue_sid_idx` ON `queues` (`queue_sid`);
CREATE INDEX `registrations_registration_sid_idx` ON `registrations` (`registration_sid`);
CREATE INDEX `queue_members_queue_member_sid_idx` ON `queue_members` (`queue_member_sid`);
ALTER TABLE `queue_members` ADD FOREIGN KEY call_sid_idxfk_1 (`call_sid`) REFERENCES `calls` (`call_sid`);
ALTER TABLE `queue_members` ADD FOREIGN KEY queue_sid_idxfk (`queue_sid`) REFERENCES `queues` (`queue_sid`);
CREATE INDEX `calls_call_sid_idx` ON `calls` (`call_sid`);
ALTER TABLE `calls` ADD FOREIGN KEY parent_call_sid_idxfk (`parent_call_sid`) REFERENCES `calls` (`call_sid`);
ALTER TABLE `calls` ADD FOREIGN KEY application_sid_idxfk_1 (`application_sid`) REFERENCES `applications` (`application_sid`);
CREATE INDEX `calls_phone_number_sid_idx` ON `calls` (`phone_number_sid`);
ALTER TABLE `calls` ADD FOREIGN KEY phone_number_sid_idxfk (`phone_number_sid`) REFERENCES `phone_numbers` (`phone_number_sid`);
ALTER TABLE `calls` ADD FOREIGN KEY inbound_user_sid_idxfk (`inbound_user_sid`) REFERENCES `registrations` (`registration_sid`);
ALTER TABLE `calls` ADD FOREIGN KEY outbound_user_sid_idxfk (`outbound_user_sid`) REFERENCES `registrations` (`registration_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`);
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_2 (`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 `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`);
ALTER TABLE `subscriptions` ADD FOREIGN KEY registration_sid_idxfk (`registration_sid`) REFERENCES `registrations` (`registration_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`);
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 `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
ALTER TABLE `phone_numbers` 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`);
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);

View File

@@ -0,0 +1,10 @@
insert into service_providers (service_provider_sid, name, root_domain, registration_hook, hook_basic_auth_user, hook_basic_auth_password)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook, hook_basic_auth_user, hook_basic_auth_password)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
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.38.0.20', true, true);
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound)
values ('efbc4830-57cd-4c78-a56f-d64fdf210fe8', '287c1452-620d-4195-9f19-c9814ef90d78', '3.3.3.3', 5062, false, true);

View File

@@ -0,0 +1,3 @@
DROP DATABASE jambones_test;
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'jambones_test'@'localhost';
DROP USER 'jambones_test'@'localhost';

View File

@@ -1,3 +1,5 @@
require('./docker_start');
require('./create-test-db');
require('./sip-tests');
require('./remove-test-db');
require('./docker_stop');

11
test/remove-test-db.js Normal file
View File

@@ -0,0 +1,11 @@
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();
});
});

View File

@@ -0,0 +1,132 @@
<?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:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
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:[service]@jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-device-unknown-user
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
INVITE sip:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 2 INVITE
[authentication username=john password=2222]
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
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="403">
</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:[service]@jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 ACK
Subject: uac-device-unknown-user
Content-Length: 0
]]>
</send>
<!-- 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>

View File

@@ -0,0 +1,76 @@
<?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:[service]@jambones.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
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="403">
</recv>
<send>
<![CDATA[
ACK sip:[service]@jambones.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-device-unknown-user
Content-Length: 0
]]>
</send>
</scenario>

View File

@@ -0,0 +1,132 @@
<?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:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
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:[service]@jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-device-unknown-user
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
INVITE sip:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 2 INVITE
[authentication username=jay password=1234]
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-device-unknown-user
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="403">
</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:[service]@jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 ACK
Subject: uac-device-unknown-user
Content-Length: 0
]]>
</send>
<!-- 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>

View File

@@ -0,0 +1,187 @@
<?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:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-device-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:[service]@jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Subject: uac-pcap-device-success
Content-Length: 0
]]>
</send>
<send retrans="500">
<![CDATA[
INVITE sip:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 2 INVITE
[authentication username=john password=1234]
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-device-success
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[local_ip_type] [local_ip]
t=0 0
m=audio [auto_media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-11,16
]]>
</send>
<recv response="100" optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="200" rtd="true" crlf="true">
</recv>
<!-- 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:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 ACK
Subject: uac-pcap-device-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="1000"/>
<send retrans="500">
<![CDATA[
INFO sip:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 3 INFO
Max-Forwards: 70
Event: X-custom-Event
Subject: uac-pcap-device-success
Content-Type: application/text
Content-Length: [len]
foobar
]]>
</send>
<recv response="200" crlf="true">
</recv>
<send retrans="500">
<![CDATA[
BYE sip:[service]@jambonz.org 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: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 4 BYE
Max-Forwards: 70
Subject: uac-pcap-device-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>

View File

@@ -87,10 +87,13 @@
crlf="true">
</recv>
<recv request="INFO" optional="true" next="1">
</recv>
<recv request="BYE">
</recv>
<send>
<send next="2">
<![CDATA[
SIP/2.0 200 OK
@@ -105,16 +108,23 @@
]]>
</send>
<!-- 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"/>
<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
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
]]>
</send>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
<label id="2"/>
</scenario>

View File

@@ -31,6 +31,30 @@ test('incoming call tests', (t) => {
.then(() => {
return t.pass('incoming call from authenticated device completed successfully');
})
.then(() => {
return sippUac('uac-device-unknown-user.xml', '172.38.0.30');
})
.then(() => {
return t.pass('unknown user is rejected with a 403');
})
.then(() => {
return sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
})
.then(() => {
return t.pass('unknown realm is rejected with a 403');
})
.then(() => {
return sippUac('uac-device-invalid-password.xml', '172.38.0.30');
})
.then(() => {
return t.pass('invalid password for valid user is rejected with a 403');
})
.then(() => {
return sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30');
})
.then(() => {
return t.pass('handles in-dialog requests');
})
.then(() => {
srf.disconnect();
t.end();