mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2025-12-19 04:37:43 +00:00
merge of features from hosted fork (#9)
major merge of features from the hosted branch that was created temporarily during the initial launch of jambonz.org
This commit is contained in:
124
app.js
124
app.js
@@ -5,28 +5,38 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
|
||||
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
||||
assert.ok(process.env.JAMBONES_RTPENGINES, 'missing DRACHTIO_SECRET env var');
|
||||
|
||||
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
|
||||
const Srf = require('drachtio-srf');
|
||||
const srf = new Srf('sbc-inbound');
|
||||
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 stats = srf.locals.stats = new StatsCollector(logger);
|
||||
srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger);
|
||||
const {getRtpEngine} = require('@jambonz/rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, {
|
||||
emitter: srf.locals.stats
|
||||
const {
|
||||
queryCdrs,
|
||||
writeCdrs,
|
||||
writeAlerts,
|
||||
AlertType
|
||||
} = require('@jambonz/time-series')(logger, {
|
||||
host: process.env.JAMBONES_TIME_SERIES_HOST,
|
||||
commitSize: 50,
|
||||
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
|
||||
});
|
||||
srf.locals.getRtpEngine = getRtpEngine;
|
||||
const activeCallIds = srf.locals.activeCallIds = new Map();
|
||||
logger.info('starting..');
|
||||
const StatsCollector = require('@jambonz/stats-collector');
|
||||
const stats = new StatsCollector(logger);
|
||||
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
|
||||
const rtpServers = [];
|
||||
|
||||
const {
|
||||
pool,
|
||||
lookupAuthHook,
|
||||
lookupSipGatewayBySignalingAddress,
|
||||
addSbcAddress
|
||||
addSbcAddress,
|
||||
lookupAccountByPhoneNumber,
|
||||
lookupAppByTeamsTenant,
|
||||
lookupAccountBySipRealm,
|
||||
lookupAccountBySid,
|
||||
lookupAccountCapacitiesBySid
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
@@ -34,12 +44,46 @@ const {
|
||||
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||
}, logger);
|
||||
const {createSet, retrieveSet, incrKey, decrKey} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
srf.locals.dbHelpers = {
|
||||
lookupAuthHook,
|
||||
lookupSipGatewayBySignalingAddress
|
||||
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {emitter: stats});
|
||||
srf.locals = {...srf.locals,
|
||||
stats,
|
||||
queryCdrs,
|
||||
writeCdrs,
|
||||
writeAlerts,
|
||||
AlertType,
|
||||
activeCallIds: new Map(),
|
||||
getRtpEngine,
|
||||
dbHelpers: {
|
||||
pool,
|
||||
lookupAuthHook,
|
||||
lookupSipGatewayBySignalingAddress,
|
||||
lookupAccountByPhoneNumber,
|
||||
lookupAppByTeamsTenant,
|
||||
lookupAccountBySid,
|
||||
lookupAccountBySipRealm,
|
||||
lookupAccountCapacitiesBySid
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
createSet,
|
||||
incrKey,
|
||||
decrKey,
|
||||
retrieveSet
|
||||
}
|
||||
};
|
||||
const {challengeDeviceCalls, initLocals} = require('./lib/middleware')(srf, logger);
|
||||
srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger);
|
||||
const activeCallIds = srf.locals.activeCallIds;
|
||||
|
||||
const {
|
||||
initLocals,
|
||||
identifyAccount,
|
||||
checkLimits,
|
||||
challengeDeviceCalls
|
||||
} = require('./lib/middleware')(srf, logger);
|
||||
const CallSession = require('./lib/call-session');
|
||||
|
||||
if (process.env.DRACHTIO_HOST) {
|
||||
@@ -48,7 +92,12 @@ if (process.env.DRACHTIO_HOST) {
|
||||
const last = hp.split(',').pop();
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(last);
|
||||
logger.info(`connected to drachtio listening on ${hp}: adding ${arr[2]} to sbc_addresses table`);
|
||||
addSbcAddress(arr[2]);
|
||||
srf.locals.sipAddress = arr[2];
|
||||
|
||||
/* don't add IP to the general SBC table if this is a static IP for a single account */
|
||||
if (!process.env.SBC_ACCOUNT_SID) {
|
||||
addSbcAddress(arr[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -60,8 +109,8 @@ if (process.env.NODE_ENV === 'test') {
|
||||
});
|
||||
}
|
||||
|
||||
// challenge calls from devices, let calls from sip gateways through
|
||||
srf.use('invite', [initLocals, challengeDeviceCalls]);
|
||||
/* install middleware */
|
||||
srf.use('invite', [initLocals, identifyAccount, checkLimits, challengeDeviceCalls]);
|
||||
|
||||
srf.invite((req, res) => {
|
||||
if (req.has('Replaces')) {
|
||||
@@ -84,8 +133,47 @@ srf.use((req, res, next, err) => {
|
||||
res.send(500);
|
||||
});
|
||||
|
||||
/* update call stats periodically */
|
||||
setInterval(() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
||||
}, 20000);
|
||||
|
||||
const arrayCompare = (a, b) => {
|
||||
if (a.length !== b.length) return false;
|
||||
const uniqueValues = new Set([...a, ...b]);
|
||||
for (const v of uniqueValues) {
|
||||
const aCount = a.filter((e) => e === v).length;
|
||||
const bCount = b.filter((e) => e === v).length;
|
||||
if (aCount !== bCount) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/* update rtpengines periodically */
|
||||
if (process.env.JAMBONES_RTPENGINES) {
|
||||
setRtpEngines([process.env.JAMBONES_RTPENGINES]);
|
||||
}
|
||||
else {
|
||||
const getActiveRtpServers = async() => {
|
||||
try {
|
||||
const set = await retrieveSet(setNameRtp);
|
||||
const newArray = Array.from(set);
|
||||
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
|
||||
if (!arrayCompare(newArray, rtpServers)) {
|
||||
logger.info({newArray}, 'resetting active rtpengines');
|
||||
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
|
||||
rtpServers.length = 0;
|
||||
Array.prototype.push.apply(rtpServers, newArray);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, 'Error setting new rtpengines');
|
||||
}
|
||||
};
|
||||
|
||||
setInterval(() => {
|
||||
getActiveRtpServers();
|
||||
}, 30000);
|
||||
getActiveRtpServers();
|
||||
}
|
||||
|
||||
module.exports = {srf, logger};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const Emitter = require('events');
|
||||
const {makeRtpEngineOpts, SdpWantsSrtp} = require('./utils');
|
||||
const {makeRtpEngineOpts, SdpWantsSrtp, makeCallCountKey} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {parseUri, SipError} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
@@ -28,7 +28,11 @@ class CallSession extends Emitter {
|
||||
this.getRtpEngine = req.srf.locals.getRtpEngine;
|
||||
this.getFeatureServer = req.srf.locals.getFeatureServer;
|
||||
this.stats = this.srf.locals.stats;
|
||||
this.writeCdrs = this.srf.locals.writeCdrs;
|
||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||
|
||||
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
||||
this.callCountKey = makeCallCountKey(req.locals.account_sid);
|
||||
}
|
||||
|
||||
get isFromMSTeams() {
|
||||
@@ -50,14 +54,14 @@ class CallSession extends Emitter {
|
||||
this.answer = answer;
|
||||
this.del = del;
|
||||
|
||||
const featureServer = this.getFeatureServer();
|
||||
const featureServer = await this.getFeatureServer();
|
||||
if (!featureServer) {
|
||||
this.logger.info('No available feature servers, rejecting call!');
|
||||
const tags = ['accepted:no', 'sipStatus:480', `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
return this.res.send(480);
|
||||
}
|
||||
debug(`using feature server ${featureServer}`);
|
||||
this.logger.debug(`using feature server ${featureServer}`);
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams);
|
||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||
@@ -97,6 +101,7 @@ class CallSession extends Emitter {
|
||||
const headers = {
|
||||
'From': createBLegFromHeader(this.req),
|
||||
'To': this.req.get('To'),
|
||||
'X-Account-Sid': this.req.locals.account_sid,
|
||||
'X-CID': this.req.get('Call-ID'),
|
||||
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
||||
};
|
||||
@@ -154,6 +159,7 @@ class CallSession extends Emitter {
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('call connected successfully to feature server');
|
||||
debug('call connected successfully to feature server');
|
||||
this._setHandlers({uas, uac});
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -175,30 +181,60 @@ class CallSession extends Emitter {
|
||||
_setDlgHandlers(dlg) {
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
dlg.on('destroy', () => {
|
||||
debug('call ended with normal termination');
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
|
||||
//re-invite
|
||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||
}
|
||||
|
||||
_setHandlers({uas, uac}) {
|
||||
this.emit('connected');
|
||||
const callStart = Date.now();
|
||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
|
||||
if (this.req.locals.cdr) {
|
||||
this.req.locals.cdr = {
|
||||
...this.req.locals.cdr,
|
||||
answered: true,
|
||||
answered_at: callStart
|
||||
};
|
||||
}
|
||||
this.uas = uas;
|
||||
this.uac = uac;
|
||||
[uas, uac].forEach((dlg) => {
|
||||
//hangup
|
||||
dlg.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
dlg.other.destroy().catch((e) => {});
|
||||
|
||||
if (process.env.JAMBONES_HOSTING) {
|
||||
this.decrKey(this.callCountKey)
|
||||
.then((count) => this.logger.debug({key: this.callCountKey},
|
||||
`after hangup there are ${count} active calls for this account`))
|
||||
.catch((err) => this.logger.error({err}, 'Error decrementing call count'));
|
||||
}
|
||||
|
||||
/* write cdr for connected call */
|
||||
if (this.req.locals.cdr) {
|
||||
const now = Date.now();
|
||||
const trunk = ['trunk', 'teams'].includes(this.req.locals.originator) ?
|
||||
this.req.locals.carrier :
|
||||
this.req.locals.originator;
|
||||
this.writeCdrs({...this.req.locals.cdr,
|
||||
terminated_at: now,
|
||||
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
|
||||
sip_status: 200,
|
||||
duration: Math.floor((now - callStart) / 1000),
|
||||
trunk
|
||||
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
|
||||
}
|
||||
});
|
||||
});
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
|
||||
130
lib/db-utils.js
Normal file
130
lib/db-utils.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const assert = require('assert');
|
||||
const CIDRMatcher = require('cidr-matcher');
|
||||
const {parseUri} = require('drachtio-srf');
|
||||
const {normalizeDID} = require('./utils');
|
||||
|
||||
const sqlSelectAllCarriersForAccountByRealm =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
||||
WHERE acc.sip_realm = ?
|
||||
AND vc.account_sid = acc.account_sid
|
||||
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
||||
|
||||
const sqlSelectAllGatewaysForSP =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||
FROM sip_gateways sg, voip_carriers vc
|
||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
AND vc.service_provider_sid IS NOT NULL
|
||||
AND vc.is_active = 1`;
|
||||
|
||||
const sqlCarriersForAccountBySid =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
||||
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
||||
WHERE acc.account_sid = ?
|
||||
AND vc.account_sid = acc.account_sid
|
||||
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
||||
|
||||
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ?';
|
||||
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
|
||||
|
||||
const sqlQueryApplicationByDid = `
|
||||
SELECT * FROM phone_numbers
|
||||
WHERE number = ?
|
||||
AND voip_carrier_sid = ?`;
|
||||
|
||||
const gatewayMatchesSourceAddress = (source_address, gw) => {
|
||||
if (32 === gw.netmask && gw.ipv4 === source_address) return true;
|
||||
if (gw.netmask < 32) {
|
||||
const matcher = new CIDRMatcher([`${gw.ipv4}/${gw.netmask}`]);
|
||||
return matcher.contains(source_address);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = (srf, logger) => {
|
||||
const {pool} = srf.locals.dbHelpers;
|
||||
const pp = pool.promise();
|
||||
|
||||
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
|
||||
const did = normalizeDID(req.calledNumber);
|
||||
|
||||
try {
|
||||
const [r] = await pp.query(sqlQueryApplicationByDid, [did, voip_carrier_sid]);
|
||||
if (0 === r.length) return null;
|
||||
return r[0].application_sid;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getApplicationForDidAndCarrier');
|
||||
}
|
||||
};
|
||||
|
||||
const wasOriginatedFromCarrier = async(req) => {
|
||||
const failure = {fromCarrier: false};
|
||||
const uri = parseUri(req.uri);
|
||||
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
||||
|
||||
if (isDotDecimal) {
|
||||
if (process.env.JAMBONES_HOSTING) {
|
||||
if (!process.env.SBC_ACCOUNT_SID) return failure;
|
||||
|
||||
/* look for carrier only within that account */
|
||||
const [r] = await pp.query(sqlCarriersForAccountBySid,
|
||||
[process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]);
|
||||
if (0 === r.length) return failure;
|
||||
return {fromCarrier: true, gateway: r[0], account_sid: process.env.SBC_ACCOUNT_SID};
|
||||
}
|
||||
else {
|
||||
/* we may have a carrier at the service provider level */
|
||||
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
||||
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||
if (matches.length) {
|
||||
/* we have one or more carriers that match. Now we need to find one with a provisioned phone number */
|
||||
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
|
||||
const did = normalizeDID(req.calledNumber);
|
||||
const sql = `SELECT * FROM phone_numbers WHERE number = ${did} AND voip_carrier_sid IN (${vc_sids})`;
|
||||
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
|
||||
|
||||
const [r] = await pp.query(sql);
|
||||
if (0 === r.length) {
|
||||
/* came from a carrier, but number is not provisioned */
|
||||
return {fromCarrier: true};
|
||||
}
|
||||
const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid);
|
||||
const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid);
|
||||
assert(accounts.length);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway,
|
||||
account_sid: r[0].account_sid,
|
||||
application_sid: r[0].application_sid,
|
||||
account: accounts[0]
|
||||
};
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
|
||||
/* get all the carriers and gateways for the account owning this sip realm */
|
||||
const [gw] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
||||
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||
if (selected) {
|
||||
const [a] = await pp.query(sqlAccountByRealm, uri.host);
|
||||
if (0 === a.length) return failure;
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: selected,
|
||||
account_sid: a[0].account_sid,
|
||||
application_sid: selected.application_sid,
|
||||
account: a[0]
|
||||
};
|
||||
}
|
||||
return failure;
|
||||
};
|
||||
|
||||
return {
|
||||
wasOriginatedFromCarrier,
|
||||
getApplicationForDidAndCarrier
|
||||
};
|
||||
};
|
||||
@@ -1,100 +1,24 @@
|
||||
const contacts = new Map();
|
||||
const noopLogger = {info: () => {}, error: () => {}};
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
|
||||
module.exports = (srf, logger) => {
|
||||
logger = logger || noopLogger;
|
||||
let dynamic = true;
|
||||
const {retrieveSet, createSet} = srf.locals.realtimeDbHelpers;
|
||||
let idx = 0;
|
||||
const stats = srf.locals.stats;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
|
||||
const {createSet} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
srf.options((req, res) => {
|
||||
res.send(200);
|
||||
if (req.has('X-FS-Status')) {
|
||||
const uri = `${req.source_address}:${req.source_port}`;
|
||||
const status = req.get('X-FS-Status');
|
||||
const calls = req.has('X-FS-Calls') ? parseInt(req.get('X-FS-Calls')) : 0;
|
||||
if (status === 'open') {
|
||||
const adding = !contacts.has(uri);
|
||||
debug(`Feature server at ${uri} has ${calls} calls`);
|
||||
contacts.set(uri, {pingTime: new Date(), calls: calls});
|
||||
if (adding) {
|
||||
logger.info(`adding feature server at ${uri}: ${[...contacts.keys()]}`);
|
||||
stats.gauge('sbc.featureservers.count', contacts.size);
|
||||
const featureServerIps = [...contacts.keys()].map((u) => {
|
||||
const arr = /^(.*):\d+$/.exec(u);
|
||||
return arr[1];
|
||||
});
|
||||
createSet(setName, new Set(featureServerIps));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (contacts.has(uri)) {
|
||||
contacts.delete(uri);
|
||||
logger.info(`removing feature server at ${uri}, leaving ${[...contacts.keys()]}`);
|
||||
stats.gauge('sbc.featureservers.count', contacts.size);
|
||||
const featureServerIps = [...contacts.keys()].map((u) => {
|
||||
const arr = /^(.*):\d+$/.exec(u);
|
||||
return arr[1];
|
||||
});
|
||||
createSet(setName, new Set(featureServerIps));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// NB: this should only be used for test suite at this point
|
||||
// in actual scenario OPTIONS pings from feature servers are used
|
||||
if (process.env.NODE_ENV === 'test' && process.env.JAMBONES_FEATURE_SERVERS) {
|
||||
dynamic = false;
|
||||
process.env.JAMBONES_FEATURE_SERVERS
|
||||
.split(',')
|
||||
.map((hp) => hp.trim())
|
||||
.forEach((uri) => contacts.set(uri, {active: 0, calls: 0}));
|
||||
debug(`using static list of feature servers: ${[ ...contacts]}`);
|
||||
if ('test' === process.env.NODE_ENV) {
|
||||
createSet(setName, [process.env.JAMBONES_FEATURE_SERVERS]);
|
||||
}
|
||||
|
||||
if (dynamic) {
|
||||
const CHECK_INTERVAL = 35;
|
||||
setInterval(() => {
|
||||
const dead = [];
|
||||
const deadline = Date.now() - 90000;
|
||||
for (const obj of contacts) {
|
||||
if (obj[1].pingTime.getTime() < deadline) dead.push(obj[0]);
|
||||
return async() => {
|
||||
try {
|
||||
const fs = await retrieveSet(setName);
|
||||
if (0 === fs.length) {
|
||||
logger.info('No available feature servers to handle incoming call');
|
||||
return;
|
||||
}
|
||||
dead.forEach((uri) => {
|
||||
logger.info(`removing feature server at ${uri} due to lack of OPTIONS ping`);
|
||||
contacts.delete(uri);
|
||||
});
|
||||
|
||||
const keys = [ ...contacts.keys() ];
|
||||
debug({keys}, `there are ${keys.length} feature servers online`);
|
||||
stats.gauge('sbc.featureservers.count', contacts.size);
|
||||
}, CHECK_INTERVAL * 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
let selectedUri;
|
||||
|
||||
if (dynamic) {
|
||||
const featureServers = [ ...contacts].map((o) => Object.assign({}, {uri: o[0]}, o[1]));
|
||||
debug({featureServers}, 'selecting feature servers with least calls');
|
||||
const fs = featureServers.sort((a, b) => (a.calls - b.calls)).shift();
|
||||
if (!fs) logger.info('No available feature servers!');
|
||||
else selectedUri = fs.uri;
|
||||
logger.debug({fs}, `retrieved ${setName}`);
|
||||
return fs[idx++ % fs.length];
|
||||
} catch (err) {
|
||||
logger.error({err}, `Error retrieving ${setName}`);
|
||||
}
|
||||
else {
|
||||
const featureServers = [ ...contacts].map((o) => o[0]);
|
||||
debug({featureServers}, 'selecting feature servers from static list');
|
||||
selectedUri = featureServers[idx++ % featureServers.length];
|
||||
debug(`selected ${selectedUri}`);
|
||||
}
|
||||
return selectedUri;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,29 +1,79 @@
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const assert = require('assert');
|
||||
const Emitter = require('events');
|
||||
const parseUri = require('drachtio-srf').parseUri;
|
||||
const {makeCallCountKey} = require('./utils');
|
||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
||||
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
||||
[];
|
||||
|
||||
class AuthOutcomeReporter extends Emitter {
|
||||
constructor(stats) {
|
||||
super();
|
||||
this.on('regHookOutcome', ({rtt, status}) => {
|
||||
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
|
||||
});
|
||||
}
|
||||
}
|
||||
const initCdr = (req) => {
|
||||
return {
|
||||
from: req.callingNumber,
|
||||
to: req.calledNumber,
|
||||
sip_callid: req.get('Call-ID'),
|
||||
duration: 0,
|
||||
attempted_at: Date.now(),
|
||||
direction: 'inbound',
|
||||
host: req.srf.locals.sipAddress,
|
||||
remote_host: req.source_address,
|
||||
answered: false
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = function(srf, logger) {
|
||||
const {lookupSipGatewayBySignalingAddress, lookupAuthHook} = srf.locals.dbHelpers;
|
||||
const {stats} = srf.locals;
|
||||
class AuthOutcomeReporter extends Emitter {
|
||||
constructor(stats) {
|
||||
super();
|
||||
this
|
||||
.on('regHookOutcome', ({rtt, status}) => {
|
||||
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
|
||||
})
|
||||
.on('error', async(err, req) => {
|
||||
const {account_sid} = req.locals;
|
||||
const {writeAlerts, AlertType} = req.srf.locals;
|
||||
if (account_sid) {
|
||||
let opts = {account_sid};
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
|
||||
}
|
||||
else if (err.code === 'ENOTFOUND') {
|
||||
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
|
||||
}
|
||||
else if (err.name === 'StatusError') {
|
||||
opts = {...opts, alert_type: AlertType.WEBHOOK_STATUS_FAILURE, url: err.hook, status: err.statusCode};
|
||||
}
|
||||
|
||||
if (opts.alert_type) {
|
||||
try {
|
||||
await writeAlerts(opts);
|
||||
} catch (err) {
|
||||
logger.error({err, opts}, 'Error writing alert');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
lookupAuthHook,
|
||||
lookupAppByTeamsTenant,
|
||||
lookupAccountBySipRealm,
|
||||
lookupAccountBySid,
|
||||
lookupAccountCapacitiesBySid
|
||||
} = srf.locals.dbHelpers;
|
||||
const {stats, writeCdrs} = srf.locals;
|
||||
const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, {
|
||||
blacklistUnknownRealms: true,
|
||||
emitter: new AuthOutcomeReporter(stats)
|
||||
});
|
||||
const {wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = require('./db-utils')(srf, logger);
|
||||
|
||||
function initLocals(req, res, next) {
|
||||
|
||||
const initLocals = (req, res, next) => {
|
||||
req.locals = req.locals || {};
|
||||
req.locals.cdr = initCdr(req);
|
||||
const callId = req.get('Call-ID');
|
||||
req.on('cancel', () => {
|
||||
logger.info({callId}, 'caller hungup before connecting to feature server');
|
||||
@@ -33,38 +83,193 @@ module.exports = function(srf, logger) {
|
||||
stats.increment('sbc.terminations', tags);
|
||||
});
|
||||
stats.increment('sbc.invites', ['direction:inbound']);
|
||||
next();
|
||||
}
|
||||
|
||||
async function challengeDeviceCalls(req, res, next) {
|
||||
/* write cdr for non-success response here */
|
||||
res.once('end', ({status}) => {
|
||||
if (req.locals.cdr && req.locals.cdr.account_sid && status > 200 && 401 !== status) {
|
||||
const trunk = ['trunk', 'teams'].includes(req.locals.originator) ? req.locals.carrier : req.locals.originator;
|
||||
writeCdrs({...req.locals.cdr,
|
||||
terminated_at: Date.now(),
|
||||
termination_reason: status === 487 === status ? 'caller abandoned' : 'failed',
|
||||
sip_status: status,
|
||||
trunk
|
||||
}).catch((err) => logger.error({err}, 'Error writing cdr for call failure'));
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
const identifyAccount = async(req, res, next) => {
|
||||
try {
|
||||
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
|
||||
if (gateway) {
|
||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
||||
req.locals.originator = 'trunk';
|
||||
req.locals.carrier = gateway.name;
|
||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
||||
return next();
|
||||
|
||||
const {fromCarrier, gateway, account_sid, application_sid, account} = await wasOriginatedFromCarrier(req);
|
||||
/**
|
||||
* calls come from 3 sources:
|
||||
* (1) A carrier
|
||||
* (2) Microsoft Teams
|
||||
* (3) A SIP user
|
||||
*/
|
||||
if (fromCarrier) {
|
||||
if (!gateway) {
|
||||
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
|
||||
return res.send(404, 'Number Not Provisioned');
|
||||
}
|
||||
logger.debug({gateway}, 'identifyAccount: incoming call from gateway');
|
||||
|
||||
/* check for phone number level routing */
|
||||
const sid = application_sid || await getApplicationForDidAndCarrier(req, gateway.voip_carrier_sid);
|
||||
req.locals = {
|
||||
originator: 'trunk',
|
||||
carrier: gateway.name,
|
||||
application_sid: sid || gateway.application_sid,
|
||||
account_sid,
|
||||
account,
|
||||
...req.locals
|
||||
};
|
||||
}
|
||||
if (msProxyIps.includes(req.source_address)) {
|
||||
logger.debug({source_address: req.source_address}, 'challengeDeviceCalls: incoming call from Microsoft Teams');
|
||||
else if (msProxyIps.includes(req.source_address)) {
|
||||
logger.debug({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
|
||||
const uri = parseUri(req.uri);
|
||||
req.locals.originator = 'teams';
|
||||
req.locals.carrier = 'Microsoft Teams';
|
||||
req.locals.msTeamsTenantFqdn = uri.host;
|
||||
return next();
|
||||
|
||||
const app = await lookupAppByTeamsTenant(uri.host);
|
||||
if (!app) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:404']);
|
||||
return res.send(404, {headers: {'X-Reason': 'no configured application'}});
|
||||
}
|
||||
|
||||
req.locals = {
|
||||
originator: 'teams',
|
||||
carrier: 'Microsoft Teams',
|
||||
msTeamsTenantFqdn: uri.host,
|
||||
account_sid: app.account_sid,
|
||||
...req.locals
|
||||
};
|
||||
}
|
||||
req.locals.originator = 'device';
|
||||
else {
|
||||
req.locals.originator = 'user';
|
||||
const uri = parseUri(req.uri);
|
||||
logger.debug({source_address: req.source_address, realm: uri.host},
|
||||
'identifyAccount: incoming user call');
|
||||
const account = await lookupAccountBySipRealm(uri.host);
|
||||
if (!account) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:404']);
|
||||
return res.send(404);
|
||||
}
|
||||
|
||||
/* if this is a dedicated SBC (static IP) only take calls for that account's sip realm */
|
||||
if (process.env.SBC_ACCOUNT_SID && account.account_sid !== process.env.SBC_ACCOUNT_SID) {
|
||||
logger.info(
|
||||
`identifyAccount: static IP for ${process.env.SBC_ACCOUNT_SID} but call for ${account.account_sid}`);
|
||||
stats.increment('sbc.terminations', ['sipStatus:404']);
|
||||
delete req.locals.cdr;
|
||||
return res.send(404);
|
||||
}
|
||||
req.locals = {
|
||||
account_sid: account.account_sid,
|
||||
account,
|
||||
webhook_secret: account.webhook_secret,
|
||||
...req.locals
|
||||
};
|
||||
}
|
||||
assert(req.locals.account_sid);
|
||||
req.locals.cdr.account_sid = req.locals.account_sid;
|
||||
|
||||
if (!req.locals.account) {
|
||||
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
|
||||
}
|
||||
|
||||
if (!req.locals.account.is_active) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:503']);
|
||||
return res.send(503, {headers: {'X-Reason': 'Account exists but is inactive'}});
|
||||
}
|
||||
|
||||
if (req.locals.account.disable_cdrs) {
|
||||
logger.info({account_sid: req.locals.account_sid}, 'Not writing CDRs for this account');
|
||||
delete req.locals.cdr;
|
||||
}
|
||||
|
||||
req.locals.logger = logger.child({callId: req.get('Call-ID'), account_sid: req.locals.account_sid});
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||
logger.error(err, `${req.get('Call-ID')} database error for inbound call`);
|
||||
res.send(500);
|
||||
}
|
||||
};
|
||||
|
||||
const checkLimits = async(req, res, next) => {
|
||||
if (!process.env.JAMBONES_HOSTING) return next(); // skip
|
||||
|
||||
const {incrKey, decrKey} = req.srf.locals.realtimeDbHelpers;
|
||||
const {logger, account_sid} = req.locals;
|
||||
const {writeAlerts, AlertType} = req.srf.locals;
|
||||
assert(account_sid);
|
||||
const key = makeCallCountKey(account_sid);
|
||||
|
||||
/* decrement count if INVITE is later rejected */
|
||||
res.once('end', ({status}) => {
|
||||
if (status > 200) {
|
||||
decrKey(key)
|
||||
.then((count) => {
|
||||
logger.debug({key}, `after rejection there are ${count} active calls for this account`);
|
||||
debug({key}, `after rejection there are ${count} active calls for this account`);
|
||||
return;
|
||||
})
|
||||
.catch((err) => logger.error({err}, 'checkLimits: decrKey err'));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
/* increment the call count */
|
||||
const calls = await incrKey(key);
|
||||
|
||||
/* compare to account's limit, though avoid db hit when call count is low */
|
||||
const minLimit = process.env.MIN_CALL_LIMIT ?
|
||||
parseInt(process.env.MIN_CALL_LIMIT) :
|
||||
0;
|
||||
logger.debug(`checkLimits: call count is now ${calls}, limit is ${minLimit}`);
|
||||
if (calls <= minLimit) return next();
|
||||
|
||||
const capacities = await lookupAccountCapacitiesBySid(account_sid);
|
||||
const limit = capacities.find((c) => c.category == 'voice_call_session');
|
||||
if (!limit) throw new Error('no account_capacities found');
|
||||
const limit_sessions = limit.quantity;
|
||||
if (calls > limit_sessions) {
|
||||
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
|
||||
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
|
||||
writeAlerts({
|
||||
alert_type: AlertType.CALL_LIMIT,
|
||||
account_sid,
|
||||
count: limit_sessions
|
||||
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
|
||||
return res.send(503, 'Maximum Calls In Progress');
|
||||
}
|
||||
next();
|
||||
} catch (err) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||
logger.error({err}, 'error checking limits error for inbound call');
|
||||
res.send(500);
|
||||
}
|
||||
};
|
||||
|
||||
const challengeDeviceCalls = async(req, res, next) => {
|
||||
try {
|
||||
/* TODO: check if this is a gateway that we have an ACL for */
|
||||
if (req.locals.originator !== 'user') return next();
|
||||
return authenticator(req, res, next);
|
||||
} catch (err) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
|
||||
res.send(500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
initLocals,
|
||||
challengeDeviceCalls
|
||||
challengeDeviceCalls,
|
||||
identifyAccount,
|
||||
checkLimits
|
||||
};
|
||||
};
|
||||
|
||||
24
lib/utils.js
24
lib/utils.js
@@ -2,14 +2,14 @@ const rtpCharacteristics = require('../data/rtp-transcoding');
|
||||
const srtpCharacteristics = require('../data/srtp-transcoding');
|
||||
let idx = 0;
|
||||
|
||||
function isWSS(req) {
|
||||
const isWSS = (req) => {
|
||||
return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws');
|
||||
}
|
||||
};
|
||||
|
||||
function getAppserver(srf) {
|
||||
const getAppserver = (srf) => {
|
||||
const len = srf.locals.featureServers.length;
|
||||
return srf.locals.featureServers[ idx++ % len];
|
||||
}
|
||||
};
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const from = req.getParsedHeader('from');
|
||||
@@ -33,13 +33,23 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
};
|
||||
}
|
||||
|
||||
function SdpWantsSrtp(sdp) {
|
||||
const SdpWantsSrtp = (sdp) => {
|
||||
return /m=audio.*SAVP/.test(sdp);
|
||||
}
|
||||
};
|
||||
|
||||
const makeCallCountKey = (sid) => `${sid}:incalls`;
|
||||
|
||||
const normalizeDID = (tel) => {
|
||||
const regex = /^\+(\d+)$/;
|
||||
const arr = regex.exec(tel);
|
||||
return arr ? arr[1] : tel;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isWSS,
|
||||
SdpWantsSrtp,
|
||||
getAppserver,
|
||||
makeRtpEngineOpts
|
||||
makeRtpEngineOpts,
|
||||
makeCallCountKey,
|
||||
normalizeDID
|
||||
};
|
||||
|
||||
963
package-lock.json
generated
963
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -20,28 +20,29 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app",
|
||||
"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 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ | ./node_modules/.bin/tap-spec",
|
||||
"test": "NODE_ENV=test JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 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=debug DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.5.12",
|
||||
"@jambonz/http-authenticator": "^0.1.6",
|
||||
"@jambonz/realtimedb-helpers": "^0.2.20",
|
||||
"@jambonz/rtpengine-utils": "^0.1.7",
|
||||
"@jambonz/stats-collector": "^0.1.4",
|
||||
"@jambonz/db-helpers": "^0.6.12",
|
||||
"@jambonz/http-authenticator": "^0.2.0",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.3",
|
||||
"@jambonz/rtpengine-utils": "^0.1.12",
|
||||
"@jambonz/stats-collector": "^0.1.5",
|
||||
"@jambonz/time-series": "^0.1.5",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.1",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.4.44",
|
||||
"drachtio-srf": "^4.4.49",
|
||||
"pino": "^6.8.0",
|
||||
"rtpengine-client": "^0.1.1"
|
||||
"rtpengine-client": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"blue-tape": "^1.0.0",
|
||||
"clear-module": "^4.1.1",
|
||||
"eslint": "^7.15.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"tap-spec": "^5.0.0"
|
||||
"tape": "^4.13.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const test = require('tape').test ;
|
||||
const test = require('tape') ;
|
||||
const exec = require('child_process').exec ;
|
||||
|
||||
test('creating jambones_test database', (t) => {
|
||||
|
||||
@@ -2,20 +2,46 @@
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS account_static_ips;
|
||||
|
||||
DROP TABLE IF EXISTS account_products;
|
||||
|
||||
DROP TABLE IF EXISTS account_subscriptions;
|
||||
|
||||
DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
DROP TABLE IF EXISTS predefined_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS account_offers;
|
||||
|
||||
DROP TABLE IF EXISTS products;
|
||||
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
||||
DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS speech_credentials;
|
||||
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
@@ -30,6 +56,41 @@ DROP TABLE IF EXISTS service_providers;
|
||||
|
||||
DROP TABLE IF EXISTS webhooks;
|
||||
|
||||
CREATE TABLE account_static_ips
|
||||
(
|
||||
account_static_ip_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
public_ipv4 VARCHAR(16) NOT NULL UNIQUE ,
|
||||
private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (account_static_ip_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_subscriptions
|
||||
(
|
||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
pending BOOLEAN NOT NULL DEFAULT false,
|
||||
effective_start_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
effective_end_date DATETIME,
|
||||
change_reason VARCHAR(255),
|
||||
stripe_subscription_id VARCHAR(56),
|
||||
stripe_payment_method_id VARCHAR(56),
|
||||
stripe_statement_descriptor VARCHAR(255),
|
||||
last4 VARCHAR(512),
|
||||
exp_month INTEGER,
|
||||
exp_year INTEGER,
|
||||
card_type VARCHAR(16),
|
||||
pending_reason VARBINARY(52),
|
||||
PRIMARY KEY (account_subscription_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE beta_invite_codes
|
||||
(
|
||||
invite_code CHAR(6) NOT NULL UNIQUE ,
|
||||
in_use BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (invite_code)
|
||||
);
|
||||
|
||||
CREATE TABLE call_routes
|
||||
(
|
||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -38,7 +99,16 @@ account_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(255) NOT NULL,
|
||||
application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
|
||||
) COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE dns_records
|
||||
(
|
||||
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
record_type VARCHAR(6) NOT NULL,
|
||||
record_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (dns_record_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
@@ -49,6 +119,61 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE predefined_carriers
|
||||
(
|
||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
requires_static_ip BOOLEAN NOT NULL DEFAULT false,
|
||||
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
|
||||
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||
register_username VARCHAR(64),
|
||||
register_sip_realm VARCHAR(64),
|
||||
register_password VARCHAR(64),
|
||||
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
|
||||
inbound_auth_username VARCHAR(64),
|
||||
inbound_auth_password VARCHAR(64),
|
||||
diversion VARCHAR(32),
|
||||
PRIMARY KEY (predefined_carrier_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE predefined_sip_gateways
|
||||
(
|
||||
predefined_sip_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
predefined_carrier_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (predefined_sip_gateway_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE products
|
||||
(
|
||||
product_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
PRIMARY KEY (product_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_products
|
||||
(
|
||||
account_product_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_subscription_sid CHAR(36) NOT NULL,
|
||||
product_sid CHAR(36) NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_product_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_offers
|
||||
(
|
||||
account_offer_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
product_sid CHAR(36) NOT NULL,
|
||||
stripe_product_id VARCHAR(56) NOT NULL,
|
||||
PRIMARY KEY (account_offer_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE api_keys
|
||||
(
|
||||
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -59,7 +184,16 @@ expires_at TIMESTAMP NULL DEFAULT NULL,
|
||||
last_used TIMESTAMP NULL DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (api_key_sid)
|
||||
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
|
||||
) COMMENT='An authorization token that is used to access the REST api';
|
||||
|
||||
CREATE TABLE sbc_addresses
|
||||
(
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
service_provider_sid CHAR(36),
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE ms_teams_tenants
|
||||
(
|
||||
@@ -71,64 +205,121 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
CREATE TABLE sbc_addresses
|
||||
CREATE TABLE signup_history
|
||||
(
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (email)
|
||||
);
|
||||
|
||||
CREATE TABLE smpp_addresses
|
||||
(
|
||||
smpp_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
use_tls BOOLEAN NOT NULL DEFAULT 0,
|
||||
is_primary BOOLEAN NOT NULL DEFAULT 1,
|
||||
service_provider_sid CHAR(36),
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
PRIMARY KEY (smpp_address_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE speech_credentials
|
||||
(
|
||||
speech_credential_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
vendor VARCHAR(32) NOT NULL,
|
||||
credential VARCHAR(8192) NOT NULL,
|
||||
use_for_tts BOOLEAN DEFAULT true,
|
||||
use_for_stt BOOLEAN DEFAULT true,
|
||||
last_used DATETIME,
|
||||
last_tested DATETIME,
|
||||
tts_tested_ok BOOLEAN,
|
||||
stt_tested_ok BOOLEAN,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (speech_credential_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name CHAR(36) NOT NULL UNIQUE ,
|
||||
hashed_password VARCHAR(1024) NOT NULL,
|
||||
salt CHAR(16) NOT NULL,
|
||||
force_change BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
pending_email VARCHAR(255),
|
||||
phone VARCHAR(20) UNIQUE ,
|
||||
hashed_password VARCHAR(1024),
|
||||
account_sid CHAR(36),
|
||||
service_provider_sid CHAR(36),
|
||||
force_change BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
provider VARCHAR(255) NOT NULL,
|
||||
provider_userid VARCHAR(255),
|
||||
scope VARCHAR(16) NOT NULL DEFAULT 'read-write',
|
||||
phone_activation_code VARCHAR(16),
|
||||
email_activation_code VARCHAR(16),
|
||||
email_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE voip_carriers
|
||||
(
|
||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
description VARCHAR(255),
|
||||
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
|
||||
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a sip trunk that is associated with a specific account',
|
||||
service_provider_sid CHAR(36),
|
||||
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
|
||||
e164_leading_plus BOOLEAN NOT NULL DEFAULT false,
|
||||
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
|
||||
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||
register_username VARCHAR(64),
|
||||
register_sip_realm VARCHAR(64),
|
||||
register_password VARCHAR(64),
|
||||
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
|
||||
inbound_auth_username VARCHAR(64),
|
||||
inbound_auth_password VARCHAR(64),
|
||||
diversion VARCHAR(32),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
smpp_system_id VARCHAR(255),
|
||||
smpp_password VARCHAR(64),
|
||||
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
||||
smpp_inbound_system_id VARCHAR(255),
|
||||
smpp_inbound_password VARCHAR(64),
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE smpp_gateways
|
||||
(
|
||||
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(128) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 2775,
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
is_primary BOOLEAN NOT NULL DEFAULT 1,
|
||||
inbound BOOLEAN NOT NULL DEFAULT 0 COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL DEFAULT 1 COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
use_tls BOOLEAN DEFAULT 0,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (smpp_gateway_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
service_provider_sid CHAR(36) COMMENT 'if not null, this number is a test number for the associated service provider',
|
||||
PRIMARY KEY (phone_number_sid)
|
||||
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE webhooks
|
||||
(
|
||||
webhook_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
) COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE sip_gateways
|
||||
(
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
@@ -147,11 +338,22 @@ priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempt
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE webhooks
|
||||
(
|
||||
webhook_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
|
||||
CREATE TABLE applications
|
||||
(
|
||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
|
||||
service_provider_sid CHAR(36) COMMENT 'if non-null, this application is a test application that can be used by any account under the associated service provider',
|
||||
account_sid CHAR(36) COMMENT 'account that this application belongs to (if null, this is a service provider test application)',
|
||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
@@ -160,8 +362,9 @@ speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (application_sid)
|
||||
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
) COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
@@ -172,7 +375,7 @@ root_domain VARCHAR(128) UNIQUE ,
|
||||
registration_hook_sid CHAR(36),
|
||||
ms_teams_fqdn VARCHAR(255),
|
||||
PRIMARY KEY (service_provider_sid)
|
||||
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
|
||||
) COMMENT='A partition of the platform used by one service provider';
|
||||
|
||||
CREATE TABLE accounts
|
||||
(
|
||||
@@ -181,67 +384,144 @@ name VARCHAR(64) NOT NULL,
|
||||
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
|
||||
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
|
||||
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
|
||||
queue_event_hook_sid CHAR(36),
|
||||
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
plan_type ENUM('trial','free','paid') NOT NULL DEFAULT 'trial',
|
||||
stripe_customer_id VARCHAR(56),
|
||||
webhook_secret VARCHAR(36) NOT NULL,
|
||||
disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
|
||||
trial_end_date DATETIME,
|
||||
deactivated_reason VARCHAR(255),
|
||||
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
||||
PRIMARY KEY (account_sid)
|
||||
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
|
||||
) COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_sid);
|
||||
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
||||
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
||||
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
||||
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
||||
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
|
||||
|
||||
CREATE INDEX product_sid_idx ON products (product_sid);
|
||||
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
|
||||
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
|
||||
ALTER TABLE account_products ADD FOREIGN KEY account_subscription_sid_idxfk (account_subscription_sid) REFERENCES account_subscriptions (account_subscription_sid);
|
||||
|
||||
ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REFERENCES products (product_sid);
|
||||
|
||||
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
||||
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
||||
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
||||
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
||||
|
||||
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||
|
||||
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
|
||||
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||
CREATE INDEX email_idx ON signup_history (email);
|
||||
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
||||
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||
|
||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX name_idx ON users (name);
|
||||
CREATE INDEX email_idx ON users (email);
|
||||
CREATE INDEX phone_idx ON users (phone);
|
||||
CREATE INDEX account_sid_idx ON users (account_sid);
|
||||
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
||||
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX name_idx ON voip_carriers (name);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
||||
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX number_idx ON phone_numbers (number);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
CREATE INDEX voip_carrier_sid_idx ON sip_gateways (voip_carrier_sid);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
|
||||
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
@@ -257,10 +537,12 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
|
||||
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
@@ -1,22 +1,60 @@
|
||||
-- create our products
|
||||
insert into products (product_sid, name, category)
|
||||
values
|
||||
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
|
||||
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
|
||||
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
|
||||
|
||||
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
|
||||
|
||||
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
|
||||
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||
|
||||
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco');
|
||||
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
|
||||
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
|
||||
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
|
||||
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
|
||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
|
||||
|
||||
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', 'ed649e33-e771-403a-8c99-1780eabbc803');
|
||||
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/32', true, true);
|
||||
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);
|
||||
|
||||
insert into webhooks(webhook_sid, url) values('4d7ce0aa-5ead-4e61-9a6b-3daa732218b1', 'http://example.com/status');
|
||||
insert into accounts (account_sid, name, service_provider_sid)
|
||||
values ('ee9d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0');
|
||||
|
||||
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||
values ('ee9d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'delta.sip.jambonz.org');
|
||||
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
|
||||
values ('4f3853e6-d8b0-43de-ba62-d9279801695d','ee9d7d49-b3e4-4fdb-9d66-661149f717e8',0);
|
||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||
values ('61840638-ccde-4bc5-b645-5d32718c68a5', '4f3853e6-d8b0-43de-ba62-d9279801695d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
|
||||
|
||||
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||
values ('d7cc37cb-d152-49ef-a51b-485f6e917089', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'echo.sip.jambonz.org');
|
||||
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
|
||||
values ('73bbcc5d-512f-4cea-8535-9a6e3d2bd19d','d7cc37cb-d152-49ef-a51b-485f6e917089',0);
|
||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||
values ('92f137f7-4bc3-4157-b096-6817e54b1874', '73bbcc5d-512f-4cea-8535-9a6e3d2bd19d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 0);
|
||||
|
||||
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('9b1abdc7-0220-4964-bc66-32b5c70cd9ab', 'westco', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('12f401d9-cbb1-49e5-bd33-cefbca0badc3', '9b1abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.20', true, true);
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound)
|
||||
values ('1401eb72-0daf-4471-aba6-038a0a2587b3', '9b1abdc7-0220-4964-bc66-32b5c70cd9ab', '3.3.3.3', 5062, false, true);
|
||||
|
||||
|
||||
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
|
||||
values ('3b43e39f-4346-4218-8434-a53130e8be49', 'test', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
|
||||
|
||||
insert into voip_carriers (voip_carrier_sid, name, account_sid, application_sid) values ('999c1452-620d-4195-9f19-c9814ef90d78', 'customer PBX', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '3b43e39f-4346-4218-8434-a53130e8be49');
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('888a5339-c62c-4075-9e19-f4de70a96597', '999c1452-620d-4195-9f19-c9814ef90d78', '172.38.0.21', true, false);
|
||||
|
||||
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
|
||||
values ('999a5339-c62c-4075-9e19-f4de70a96597', '16173333456', '287c1452-620d-4195-9f19-c9814ef90d78', 'ed649e33-e771-403a-8c99-1780eabbc803');
|
||||
|
||||
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
|
||||
values ('f7ad205d-b92f-4363-8160-f8b5216b40d3', '15083871234', '287c1452-620d-4195-9f19-c9814ef90d78', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
retries: 15
|
||||
networks:
|
||||
sbc-inbound:
|
||||
ipv4_address: 172.38.0.2
|
||||
@@ -70,3 +70,10 @@ services:
|
||||
sbc-inbound:
|
||||
ipv4_address: 172.38.0.14
|
||||
|
||||
influxdb:
|
||||
image: influxdb:1.8-alpine
|
||||
ports:
|
||||
- "8086:8086"
|
||||
networks:
|
||||
sbc-inbound:
|
||||
ipv4_address: 172.38.0.90
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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.pass('docker is up');
|
||||
t.end(err);
|
||||
});
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<recv response="100" optional="true">
|
||||
</recv>
|
||||
|
||||
<recv response="403">
|
||||
<recv response="404">
|
||||
</recv>
|
||||
|
||||
<send>
|
||||
|
||||
71
test/scenarios/uac-pcap-carrier-max-call-limit.xml
Normal file
71
test/scenarios/uac-pcap-carrier-max-call-limit.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||
|
||||
<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:+15083871234@echo.sip.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: <sip:15083871234@echo.sip.jambonz.org>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
Max-Forwards: 70
|
||||
Subject: uac-pcap-carrier-max-call-limit
|
||||
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>
|
||||
|
||||
|
||||
<!-- 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 response="503" 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:15083871234@echo.sip.jambonz.org SIP/2.0
|
||||
[last_Via]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:15083871234@echo.sip.jambonz.org>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Max-Forwards: 70
|
||||
Subject: uac-pcap-carrier-max-call-limit
|
||||
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>
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||
INVITE sip:+16173333456@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]>
|
||||
To: <sip:16173333456@jambonz.org>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
@@ -67,10 +67,10 @@
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||
ACK sip:16173333456@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]
|
||||
To: <sip:16173333456@jambonz.org>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Max-Forwards: 70
|
||||
@@ -94,10 +94,10 @@
|
||||
<send retrans="500">
|
||||
<![CDATA[
|
||||
|
||||
BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||
BYE sip:16173333456@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]
|
||||
To: <sip:16173333456@jambonz.org>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 2 BYE
|
||||
Subject: uac-pcap-carrier-success
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const test = require('blue-tape');
|
||||
const test = require('tape');
|
||||
const { output, sippUac } = require('./sipp')('test_sbc-inbound');
|
||||
const debug = require('debug')('drachtio:sbc-inbound');
|
||||
const clearModule = require('clear-module');
|
||||
const consoleLogger = {error: console.error, info: console.log, debug: console.log};
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
@@ -15,60 +16,52 @@ function connect(connectable) {
|
||||
});
|
||||
}
|
||||
|
||||
test('incoming call tests', (t) => {
|
||||
const {srf} = require('../app');
|
||||
function waitFor(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
connect(srf)
|
||||
.then(() => {
|
||||
return sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
||||
})
|
||||
.then(() => {
|
||||
return t.pass('incoming call from carrier completed successfully');
|
||||
})
|
||||
.then(() => {
|
||||
return sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
||||
})
|
||||
.then(() => {
|
||||
return t.pass('incoming call from customer PBX completed successfully');
|
||||
})
|
||||
.then(() => {
|
||||
return sippUac('uac-pcap-device-success.xml', '172.38.0.30');
|
||||
})
|
||||
.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();
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`error received: ${err}`);
|
||||
if (srf) srf.disconnect();
|
||||
t.error(err);
|
||||
});
|
||||
test('incoming call tests', async(t) => {
|
||||
const {srf} = require('../app');
|
||||
const { queryCdrs } = srf.locals;
|
||||
|
||||
try {
|
||||
await connect(srf);
|
||||
await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
||||
t.pass('incoming call from carrier completed successfully');
|
||||
|
||||
await sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
||||
t.pass('incoming call from account-level carrier completed successfully');
|
||||
|
||||
await sippUac('uac-pcap-device-success.xml', '172.38.0.30');
|
||||
t.pass('incoming call from authenticated device completed successfully');
|
||||
|
||||
await sippUac('uac-device-unknown-user.xml', '172.38.0.30');
|
||||
t.pass('unknown user is rejected with a 403');
|
||||
|
||||
await sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
|
||||
t.pass('unknown realm is rejected with a 404');
|
||||
|
||||
await sippUac('uac-device-invalid-password.xml', '172.38.0.30');
|
||||
t.pass('invalid password for valid user is rejected with a 403');
|
||||
|
||||
await sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30');
|
||||
t.pass('handles in-dialog requests');
|
||||
|
||||
await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
|
||||
t.pass('rejects incoming call with 503 when max calls reached')
|
||||
|
||||
await waitFor(10);
|
||||
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
||||
console.log(`cdrs: ${JSON.stringify(res)}`);
|
||||
t.ok(6 === res.total, 'successfully wrote 6 cdrs for calls');
|
||||
|
||||
srf.disconnect();
|
||||
t.end();
|
||||
} catch (err) {
|
||||
console.log(`error received: ${err}`);
|
||||
if (srf) srf.disconnect();
|
||||
t.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user