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');
|
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_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.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 = require('drachtio-srf');
|
||||||
const srf = new Srf('sbc-inbound');
|
const srf = new Srf('sbc-inbound');
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
|
||||||
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
|
||||||
const logger = require('pino')(opts);
|
const logger = require('pino')(opts);
|
||||||
const StatsCollector = require('@jambonz/stats-collector');
|
const {
|
||||||
const stats = srf.locals.stats = new StatsCollector(logger);
|
queryCdrs,
|
||||||
srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger);
|
writeCdrs,
|
||||||
const {getRtpEngine} = require('@jambonz/rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, {
|
writeAlerts,
|
||||||
emitter: srf.locals.stats
|
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 StatsCollector = require('@jambonz/stats-collector');
|
||||||
const activeCallIds = srf.locals.activeCallIds = new Map();
|
const stats = new StatsCollector(logger);
|
||||||
logger.info('starting..');
|
const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
|
||||||
|
const rtpServers = [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
pool,
|
||||||
lookupAuthHook,
|
lookupAuthHook,
|
||||||
lookupSipGatewayBySignalingAddress,
|
lookupSipGatewayBySignalingAddress,
|
||||||
addSbcAddress
|
addSbcAddress,
|
||||||
|
lookupAccountByPhoneNumber,
|
||||||
|
lookupAppByTeamsTenant,
|
||||||
|
lookupAccountBySipRealm,
|
||||||
|
lookupAccountBySid,
|
||||||
|
lookupAccountCapacitiesBySid
|
||||||
} = require('@jambonz/db-helpers')({
|
} = require('@jambonz/db-helpers')({
|
||||||
host: process.env.JAMBONES_MYSQL_HOST,
|
host: process.env.JAMBONES_MYSQL_HOST,
|
||||||
user: process.env.JAMBONES_MYSQL_USER,
|
user: process.env.JAMBONES_MYSQL_USER,
|
||||||
@@ -34,12 +44,46 @@ const {
|
|||||||
database: process.env.JAMBONES_MYSQL_DATABASE,
|
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||||
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||||
}, logger);
|
}, 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 = {
|
const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {emitter: stats});
|
||||||
lookupAuthHook,
|
srf.locals = {...srf.locals,
|
||||||
lookupSipGatewayBySignalingAddress
|
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');
|
const CallSession = require('./lib/call-session');
|
||||||
|
|
||||||
if (process.env.DRACHTIO_HOST) {
|
if (process.env.DRACHTIO_HOST) {
|
||||||
@@ -48,7 +92,12 @@ if (process.env.DRACHTIO_HOST) {
|
|||||||
const last = hp.split(',').pop();
|
const last = hp.split(',').pop();
|
||||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(last);
|
const arr = /^(.*)\/(.*):(\d+)$/.exec(last);
|
||||||
logger.info(`connected to drachtio listening on ${hp}: adding ${arr[2]} to sbc_addresses table`);
|
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 {
|
else {
|
||||||
@@ -60,8 +109,8 @@ if (process.env.NODE_ENV === 'test') {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// challenge calls from devices, let calls from sip gateways through
|
/* install middleware */
|
||||||
srf.use('invite', [initLocals, challengeDeviceCalls]);
|
srf.use('invite', [initLocals, identifyAccount, checkLimits, challengeDeviceCalls]);
|
||||||
|
|
||||||
srf.invite((req, res) => {
|
srf.invite((req, res) => {
|
||||||
if (req.has('Replaces')) {
|
if (req.has('Replaces')) {
|
||||||
@@ -84,8 +133,47 @@ srf.use((req, res, next, err) => {
|
|||||||
res.send(500);
|
res.send(500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* update call stats periodically */
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
||||||
}, 20000);
|
}, 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};
|
module.exports = {srf, logger};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const {makeRtpEngineOpts, SdpWantsSrtp} = require('./utils');
|
const {makeRtpEngineOpts, SdpWantsSrtp, makeCallCountKey} = require('./utils');
|
||||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||||
const {parseUri, SipError} = require('drachtio-srf');
|
const {parseUri, SipError} = require('drachtio-srf');
|
||||||
const debug = require('debug')('jambonz:sbc-inbound');
|
const debug = require('debug')('jambonz:sbc-inbound');
|
||||||
@@ -28,7 +28,11 @@ class CallSession extends Emitter {
|
|||||||
this.getRtpEngine = req.srf.locals.getRtpEngine;
|
this.getRtpEngine = req.srf.locals.getRtpEngine;
|
||||||
this.getFeatureServer = req.srf.locals.getFeatureServer;
|
this.getFeatureServer = req.srf.locals.getFeatureServer;
|
||||||
this.stats = this.srf.locals.stats;
|
this.stats = this.srf.locals.stats;
|
||||||
|
this.writeCdrs = this.srf.locals.writeCdrs;
|
||||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||||
|
|
||||||
|
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
||||||
|
this.callCountKey = makeCallCountKey(req.locals.account_sid);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFromMSTeams() {
|
get isFromMSTeams() {
|
||||||
@@ -50,14 +54,14 @@ class CallSession extends Emitter {
|
|||||||
this.answer = answer;
|
this.answer = answer;
|
||||||
this.del = del;
|
this.del = del;
|
||||||
|
|
||||||
const featureServer = this.getFeatureServer();
|
const featureServer = await this.getFeatureServer();
|
||||||
if (!featureServer) {
|
if (!featureServer) {
|
||||||
this.logger.info('No available feature servers, rejecting call!');
|
this.logger.info('No available feature servers, rejecting call!');
|
||||||
const tags = ['accepted:no', 'sipStatus:480', `originator:${this.req.locals.originator}`];
|
const tags = ['accepted:no', 'sipStatus:480', `originator:${this.req.locals.originator}`];
|
||||||
this.stats.increment('sbc.terminations', tags);
|
this.stats.increment('sbc.terminations', tags);
|
||||||
return this.res.send(480);
|
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.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams);
|
||||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||||
@@ -97,6 +101,7 @@ class CallSession extends Emitter {
|
|||||||
const headers = {
|
const headers = {
|
||||||
'From': createBLegFromHeader(this.req),
|
'From': createBLegFromHeader(this.req),
|
||||||
'To': this.req.get('To'),
|
'To': this.req.get('To'),
|
||||||
|
'X-Account-Sid': this.req.locals.account_sid,
|
||||||
'X-CID': this.req.get('Call-ID'),
|
'X-CID': this.req.get('Call-ID'),
|
||||||
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
||||||
};
|
};
|
||||||
@@ -154,6 +159,7 @@ class CallSession extends Emitter {
|
|||||||
|
|
||||||
// successfully connected
|
// successfully connected
|
||||||
this.logger.info('call connected successfully to feature server');
|
this.logger.info('call connected successfully to feature server');
|
||||||
|
debug('call connected successfully to feature server');
|
||||||
this._setHandlers({uas, uac});
|
this._setHandlers({uas, uac});
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -175,30 +181,60 @@ class CallSession extends Emitter {
|
|||||||
_setDlgHandlers(dlg) {
|
_setDlgHandlers(dlg) {
|
||||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||||
dlg.on('destroy', () => {
|
dlg.on('destroy', () => {
|
||||||
|
debug('call ended with normal termination');
|
||||||
this.logger.info('call ended with normal termination');
|
this.logger.info('call ended with normal termination');
|
||||||
this.rtpEngineResource.destroy().catch((err) => {});
|
this.rtpEngineResource.destroy().catch((err) => {});
|
||||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||||
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
//re-invite
|
//re-invite
|
||||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||||
}
|
}
|
||||||
|
|
||||||
_setHandlers({uas, uac}) {
|
_setHandlers({uas, uac}) {
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
|
const callStart = Date.now();
|
||||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||||
this.stats.increment('sbc.terminations', tags);
|
this.stats.increment('sbc.terminations', tags);
|
||||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
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.uas = uas;
|
||||||
this.uac = uac;
|
this.uac = uac;
|
||||||
[uas, uac].forEach((dlg) => {
|
[uas, uac].forEach((dlg) => {
|
||||||
//hangup
|
|
||||||
dlg.on('destroy', () => {
|
dlg.on('destroy', () => {
|
||||||
this.logger.info('call ended with normal termination');
|
this.logger.info('call ended with normal termination');
|
||||||
this.rtpEngineResource.destroy().catch((err) => {});
|
this.rtpEngineResource.destroy().catch((err) => {});
|
||||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||||
dlg.other.destroy().catch((e) => {});
|
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));
|
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 setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||||
const noopLogger = {info: () => {}, error: () => {}};
|
|
||||||
const debug = require('debug')('jambonz:sbc-inbound');
|
|
||||||
|
|
||||||
module.exports = (srf, logger) => {
|
module.exports = (srf, logger) => {
|
||||||
logger = logger || noopLogger;
|
const {retrieveSet, createSet} = srf.locals.realtimeDbHelpers;
|
||||||
let dynamic = true;
|
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
const stats = srf.locals.stats;
|
|
||||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
|
||||||
|
|
||||||
const {createSet} = require('@jambonz/realtimedb-helpers')({
|
if ('test' === process.env.NODE_ENV) {
|
||||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
createSet(setName, [process.env.JAMBONES_FEATURE_SERVERS]);
|
||||||
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 (dynamic) {
|
return async() => {
|
||||||
const CHECK_INTERVAL = 35;
|
try {
|
||||||
setInterval(() => {
|
const fs = await retrieveSet(setName);
|
||||||
const dead = [];
|
if (0 === fs.length) {
|
||||||
const deadline = Date.now() - 90000;
|
logger.info('No available feature servers to handle incoming call');
|
||||||
for (const obj of contacts) {
|
return;
|
||||||
if (obj[1].pingTime.getTime() < deadline) dead.push(obj[0]);
|
|
||||||
}
|
}
|
||||||
dead.forEach((uri) => {
|
logger.debug({fs}, `retrieved ${setName}`);
|
||||||
logger.info(`removing feature server at ${uri} due to lack of OPTIONS ping`);
|
return fs[idx++ % fs.length];
|
||||||
contacts.delete(uri);
|
} catch (err) {
|
||||||
});
|
logger.error({err}, `Error retrieving ${setName}`);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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 debug = require('debug')('jambonz:sbc-inbound');
|
||||||
|
const assert = require('assert');
|
||||||
const Emitter = require('events');
|
const Emitter = require('events');
|
||||||
const parseUri = require('drachtio-srf').parseUri;
|
const parseUri = require('drachtio-srf').parseUri;
|
||||||
|
const {makeCallCountKey} = require('./utils');
|
||||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
||||||
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
||||||
[];
|
[];
|
||||||
|
|
||||||
class AuthOutcomeReporter extends Emitter {
|
const initCdr = (req) => {
|
||||||
constructor(stats) {
|
return {
|
||||||
super();
|
from: req.callingNumber,
|
||||||
this.on('regHookOutcome', ({rtt, status}) => {
|
to: req.calledNumber,
|
||||||
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
|
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) {
|
module.exports = function(srf, logger) {
|
||||||
const {lookupSipGatewayBySignalingAddress, lookupAuthHook} = srf.locals.dbHelpers;
|
class AuthOutcomeReporter extends Emitter {
|
||||||
const {stats} = srf.locals;
|
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, {
|
const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, {
|
||||||
blacklistUnknownRealms: true,
|
blacklistUnknownRealms: true,
|
||||||
emitter: new AuthOutcomeReporter(stats)
|
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 = req.locals || {};
|
||||||
|
req.locals.cdr = initCdr(req);
|
||||||
const callId = req.get('Call-ID');
|
const callId = req.get('Call-ID');
|
||||||
req.on('cancel', () => {
|
req.on('cancel', () => {
|
||||||
logger.info({callId}, 'caller hungup before connecting to feature server');
|
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.terminations', tags);
|
||||||
});
|
});
|
||||||
stats.increment('sbc.invites', ['direction:inbound']);
|
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 {
|
try {
|
||||||
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
|
|
||||||
if (gateway) {
|
const {fromCarrier, gateway, account_sid, application_sid, account} = await wasOriginatedFromCarrier(req);
|
||||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
/**
|
||||||
req.locals.originator = 'trunk';
|
* calls come from 3 sources:
|
||||||
req.locals.carrier = gateway.name;
|
* (1) A carrier
|
||||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
* (2) Microsoft Teams
|
||||||
return next();
|
* (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)) {
|
else if (msProxyIps.includes(req.source_address)) {
|
||||||
logger.debug({source_address: req.source_address}, 'challengeDeviceCalls: incoming call from Microsoft Teams');
|
logger.debug({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
|
||||||
const uri = parseUri(req.uri);
|
const uri = parseUri(req.uri);
|
||||||
req.locals.originator = 'teams';
|
|
||||||
req.locals.carrier = 'Microsoft Teams';
|
const app = await lookupAppByTeamsTenant(uri.host);
|
||||||
req.locals.msTeamsTenantFqdn = uri.host;
|
if (!app) {
|
||||||
return next();
|
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);
|
return authenticator(req, res, next);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||||
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
|
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
|
||||||
res.send(500);
|
res.send(500);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initLocals,
|
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');
|
const srtpCharacteristics = require('../data/srtp-transcoding');
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
|
|
||||||
function isWSS(req) {
|
const isWSS = (req) => {
|
||||||
return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws');
|
return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws');
|
||||||
}
|
};
|
||||||
|
|
||||||
function getAppserver(srf) {
|
const getAppserver = (srf) => {
|
||||||
const len = srf.locals.featureServers.length;
|
const len = srf.locals.featureServers.length;
|
||||||
return srf.locals.featureServers[ idx++ % len];
|
return srf.locals.featureServers[ idx++ % len];
|
||||||
}
|
};
|
||||||
|
|
||||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||||
const from = req.getParsedHeader('from');
|
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);
|
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 = {
|
module.exports = {
|
||||||
isWSS,
|
isWSS,
|
||||||
SdpWantsSrtp,
|
SdpWantsSrtp,
|
||||||
getAppserver,
|
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": {
|
"scripts": {
|
||||||
"start": "node app",
|
"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",
|
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||||
"jslint": "eslint app.js lib"
|
"jslint": "eslint app.js lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jambonz/db-helpers": "^0.5.12",
|
"@jambonz/db-helpers": "^0.6.12",
|
||||||
"@jambonz/http-authenticator": "^0.1.6",
|
"@jambonz/http-authenticator": "^0.2.0",
|
||||||
"@jambonz/realtimedb-helpers": "^0.2.20",
|
"@jambonz/realtimedb-helpers": "^0.4.3",
|
||||||
"@jambonz/rtpengine-utils": "^0.1.7",
|
"@jambonz/rtpengine-utils": "^0.1.12",
|
||||||
"@jambonz/stats-collector": "^0.1.4",
|
"@jambonz/stats-collector": "^0.1.5",
|
||||||
|
"@jambonz/time-series": "^0.1.5",
|
||||||
|
"cidr-matcher": "^2.1.1",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||||
"drachtio-srf": "^4.4.44",
|
"drachtio-srf": "^4.4.49",
|
||||||
"pino": "^6.8.0",
|
"pino": "^6.8.0",
|
||||||
"rtpengine-client": "^0.1.1"
|
"rtpengine-client": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"blue-tape": "^1.0.0",
|
|
||||||
"clear-module": "^4.1.1",
|
"clear-module": "^4.1.1",
|
||||||
"eslint": "^7.15.0",
|
"eslint": "^7.15.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"nyc": "^15.1.0",
|
"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 ;
|
const exec = require('child_process').exec ;
|
||||||
|
|
||||||
test('creating jambones_test database', (t) => {
|
test('creating jambones_test database', (t) => {
|
||||||
|
|||||||
@@ -2,20 +2,46 @@
|
|||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS=0;
|
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 call_routes;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS dns_records;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS lcr_routes;
|
DROP TABLE IF EXISTS lcr_routes;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS api_keys;
|
DROP TABLE IF EXISTS 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 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 users;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS smpp_gateways;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS phone_numbers;
|
DROP TABLE IF EXISTS phone_numbers;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS sip_gateways;
|
DROP TABLE IF EXISTS sip_gateways;
|
||||||
@@ -30,6 +56,41 @@ DROP TABLE IF EXISTS service_providers;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS webhooks;
|
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
|
CREATE TABLE call_routes
|
||||||
(
|
(
|
||||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -38,7 +99,16 @@ account_sid CHAR(36) NOT NULL,
|
|||||||
regex VARCHAR(255) NOT NULL,
|
regex VARCHAR(255) NOT NULL,
|
||||||
application_sid CHAR(36) NOT NULL,
|
application_sid CHAR(36) NOT NULL,
|
||||||
PRIMARY KEY (call_route_sid)
|
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
|
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)
|
PRIMARY KEY (lcr_route_sid)
|
||||||
) COMMENT='Least cost routing table';
|
) 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
|
CREATE TABLE api_keys
|
||||||
(
|
(
|
||||||
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -59,7 +184,16 @@ expires_at TIMESTAMP NULL DEFAULT NULL,
|
|||||||
last_used TIMESTAMP NULL DEFAULT NULL,
|
last_used TIMESTAMP NULL DEFAULT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (api_key_sid)
|
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
|
CREATE TABLE ms_teams_tenants
|
||||||
(
|
(
|
||||||
@@ -71,64 +205,121 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
|||||||
PRIMARY KEY (ms_teams_tenant_sid)
|
PRIMARY KEY (ms_teams_tenant_sid)
|
||||||
) COMMENT='A Microsoft Teams customer tenant';
|
) 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,
|
ipv4 VARCHAR(255) NOT NULL,
|
||||||
port INTEGER NOT NULL DEFAULT 5060,
|
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),
|
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
|
CREATE TABLE users
|
||||||
(
|
(
|
||||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
name CHAR(36) NOT NULL UNIQUE ,
|
name VARCHAR(255) NOT NULL,
|
||||||
hashed_password VARCHAR(1024) NOT NULL,
|
email VARCHAR(255) NOT NULL,
|
||||||
salt CHAR(16) NOT NULL,
|
pending_email VARCHAR(255),
|
||||||
force_change BOOLEAN NOT NULL DEFAULT TRUE,
|
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)
|
PRIMARY KEY (user_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE voip_carriers
|
CREATE TABLE voip_carriers
|
||||||
(
|
(
|
||||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
name VARCHAR(64) NOT NULL,
|
||||||
description VARCHAR(255),
|
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',
|
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,
|
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||||
register_username VARCHAR(64),
|
register_username VARCHAR(64),
|
||||||
register_sip_realm VARCHAR(64),
|
register_sip_realm VARCHAR(64),
|
||||||
register_password 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)
|
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
|
CREATE TABLE phone_numbers
|
||||||
(
|
(
|
||||||
phone_number_sid CHAR(36) UNIQUE ,
|
phone_number_sid CHAR(36) UNIQUE ,
|
||||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||||
voip_carrier_sid CHAR(36) NOT NULL,
|
voip_carrier_sid CHAR(36),
|
||||||
account_sid CHAR(36),
|
account_sid CHAR(36),
|
||||||
application_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)
|
PRIMARY KEY (phone_number_sid)
|
||||||
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
|
) 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';
|
|
||||||
|
|
||||||
CREATE TABLE sip_gateways
|
CREATE TABLE sip_gateways
|
||||||
(
|
(
|
||||||
sip_gateway_sid CHAR(36),
|
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.',
|
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',
|
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',
|
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',
|
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)
|
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||||
) COMMENT='An entry in the LCR routing list';
|
) 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
|
CREATE TABLE applications
|
||||||
(
|
(
|
||||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
name VARCHAR(64) NOT NULL,
|
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_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',
|
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 ',
|
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_synthesis_voice VARCHAR(64),
|
||||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (application_sid)
|
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
|
CREATE TABLE service_providers
|
||||||
(
|
(
|
||||||
@@ -172,7 +375,7 @@ root_domain VARCHAR(128) UNIQUE ,
|
|||||||
registration_hook_sid CHAR(36),
|
registration_hook_sid CHAR(36),
|
||||||
ms_teams_fqdn VARCHAR(255),
|
ms_teams_fqdn VARCHAR(255),
|
||||||
PRIMARY KEY (service_provider_sid)
|
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
|
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',
|
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',
|
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',
|
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',
|
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
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)
|
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);
|
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);
|
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 api_key_sid_idx ON api_keys (api_key_sid);
|
||||||
CREATE INDEX account_sid_idx ON api_keys (account_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);
|
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);
|
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_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||||
|
|
||||||
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
|
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 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 voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||||
CREATE INDEX name_idx ON voip_carriers (name);
|
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (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);
|
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 smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (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 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);
|
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 INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
||||||
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
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 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 UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||||
|
|
||||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
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);
|
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
ALTER TABLE applications ADD FOREIGN KEY call_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 account_sid_idx ON accounts (account_sid);
|
||||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
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 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);
|
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS=1;
|
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 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)
|
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');
|
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)
|
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)
|
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);
|
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 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)
|
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');
|
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 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)
|
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);
|
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:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
|
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 15
|
||||||
networks:
|
networks:
|
||||||
sbc-inbound:
|
sbc-inbound:
|
||||||
ipv4_address: 172.38.0.2
|
ipv4_address: 172.38.0.2
|
||||||
@@ -70,3 +70,10 @@ services:
|
|||||||
sbc-inbound:
|
sbc-inbound:
|
||||||
ipv4_address: 172.38.0.14
|
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 test = require('tape').test ;
|
||||||
const exec = require('child_process').exec ;
|
const exec = require('child_process').exec ;
|
||||||
const async = require('async');
|
|
||||||
|
|
||||||
test('starting docker network..', (t) => {
|
test('starting docker network..', (t) => {
|
||||||
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
|
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
|
||||||
|
t.pass('docker is up');
|
||||||
t.end(err);
|
t.end(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<recv response="100" optional="true">
|
<recv response="100" optional="true">
|
||||||
</recv>
|
</recv>
|
||||||
|
|
||||||
<recv response="403">
|
<recv response="404">
|
||||||
</recv>
|
</recv>
|
||||||
|
|
||||||
<send>
|
<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">
|
<send retrans="500">
|
||||||
<![CDATA[
|
<![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]
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
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]
|
Call-ID: [call_id]
|
||||||
CSeq: 1 INVITE
|
CSeq: 1 INVITE
|
||||||
Contact: sip:sipp@[local_ip]:[local_port]
|
Contact: sip:sipp@[local_ip]:[local_port]
|
||||||
@@ -67,10 +67,10 @@
|
|||||||
<send>
|
<send>
|
||||||
<![CDATA[
|
<![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]
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
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]
|
Call-ID: [call_id]
|
||||||
CSeq: 1 ACK
|
CSeq: 1 ACK
|
||||||
Max-Forwards: 70
|
Max-Forwards: 70
|
||||||
@@ -94,10 +94,10 @@
|
|||||||
<send retrans="500">
|
<send retrans="500">
|
||||||
<![CDATA[
|
<![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]
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
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]
|
Call-ID: [call_id]
|
||||||
CSeq: 2 BYE
|
CSeq: 2 BYE
|
||||||
Subject: uac-pcap-carrier-success
|
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 { output, sippUac } = require('./sipp')('test_sbc-inbound');
|
||||||
const debug = require('debug')('drachtio:sbc-inbound');
|
const debug = require('debug')('drachtio:sbc-inbound');
|
||||||
const clearModule = require('clear-module');
|
const clearModule = require('clear-module');
|
||||||
|
const consoleLogger = {error: console.error, info: console.log, debug: console.log};
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
@@ -15,60 +16,52 @@ function connect(connectable) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('incoming call tests', (t) => {
|
function waitFor(ms) {
|
||||||
const {srf} = require('../app');
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms * 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
connect(srf)
|
test('incoming call tests', async(t) => {
|
||||||
.then(() => {
|
const {srf} = require('../app');
|
||||||
return sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
const { queryCdrs } = srf.locals;
|
||||||
})
|
|
||||||
.then(() => {
|
try {
|
||||||
return t.pass('incoming call from carrier completed successfully');
|
await connect(srf);
|
||||||
})
|
await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
||||||
.then(() => {
|
t.pass('incoming call from carrier completed successfully');
|
||||||
return sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
|
||||||
})
|
await sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
|
||||||
.then(() => {
|
t.pass('incoming call from account-level carrier completed successfully');
|
||||||
return t.pass('incoming call from customer PBX completed successfully');
|
|
||||||
})
|
await sippUac('uac-pcap-device-success.xml', '172.38.0.30');
|
||||||
.then(() => {
|
t.pass('incoming call from authenticated device completed successfully');
|
||||||
return sippUac('uac-pcap-device-success.xml', '172.38.0.30');
|
|
||||||
})
|
await sippUac('uac-device-unknown-user.xml', '172.38.0.30');
|
||||||
.then(() => {
|
t.pass('unknown user is rejected with a 403');
|
||||||
return t.pass('incoming call from authenticated device completed successfully');
|
|
||||||
})
|
await sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
|
||||||
.then(() => {
|
t.pass('unknown realm is rejected with a 404');
|
||||||
return sippUac('uac-device-unknown-user.xml', '172.38.0.30');
|
|
||||||
})
|
await sippUac('uac-device-invalid-password.xml', '172.38.0.30');
|
||||||
.then(() => {
|
t.pass('invalid password for valid user is rejected with a 403');
|
||||||
return t.pass('unknown user is rejected with a 403');
|
|
||||||
})
|
await sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30');
|
||||||
.then(() => {
|
t.pass('handles in-dialog requests');
|
||||||
return sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
|
|
||||||
})
|
await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
|
||||||
.then(() => {
|
t.pass('rejects incoming call with 503 when max calls reached')
|
||||||
return t.pass('unknown realm is rejected with a 403');
|
|
||||||
})
|
await waitFor(10);
|
||||||
.then(() => {
|
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
||||||
return sippUac('uac-device-invalid-password.xml', '172.38.0.30');
|
console.log(`cdrs: ${JSON.stringify(res)}`);
|
||||||
})
|
t.ok(6 === res.total, 'successfully wrote 6 cdrs for calls');
|
||||||
.then(() => {
|
|
||||||
return t.pass('invalid password for valid user is rejected with a 403');
|
srf.disconnect();
|
||||||
})
|
t.end();
|
||||||
.then(() => {
|
} catch (err) {
|
||||||
return sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30');
|
console.log(`error received: ${err}`);
|
||||||
})
|
if (srf) srf.disconnect();
|
||||||
.then(() => {
|
t.error(err);
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user