mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
958c34efbc | ||
|
|
d7df23bfc1 | ||
|
|
2914c9f356 | ||
|
|
de64af278d | ||
|
|
2c751a0b36 | ||
|
|
3bb719c035 | ||
|
|
203cd0d16b | ||
|
|
c1e4324b51 | ||
|
|
b4766a4b88 | ||
|
|
0636b294b1 | ||
|
|
1d1f3d5c10 | ||
|
|
48fe42098e | ||
|
|
efaf7d28eb | ||
|
|
1ad61dc7b2 |
@@ -20,13 +20,14 @@ const {getRtpEngine} = require('jambonz-rtpengine-utils')(process.env.JAMBONES_R
|
||||
emitter: srf.locals.stats
|
||||
});
|
||||
srf.locals.getRtpEngine = getRtpEngine;
|
||||
const activeCallIds = srf.locals.activeCallIds = new Set();
|
||||
const activeCallIds = srf.locals.activeCallIds = new Map();
|
||||
logger.info('starting..');
|
||||
|
||||
const {
|
||||
lookupAuthHook,
|
||||
lookupSipGatewayBySignalingAddress
|
||||
} = require('jambonz-db-helpers')({
|
||||
lookupSipGatewayBySignalingAddress,
|
||||
addSbcAddress
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
@@ -44,7 +45,10 @@ const CallSession = require('./lib/call-session');
|
||||
if (process.env.DRACHTIO_HOST) {
|
||||
srf.connect({host: process.env.DRACHTIO_HOST, port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET });
|
||||
srf.on('connect', (err, hp) => {
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
const last = hp.split(',').pop();
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(last);
|
||||
logger.info(`connected to drachtio listening on ${hp}: adding ${arr[2]} to sbc_addresses table`);
|
||||
addSbcAddress(arr[2]);
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -60,6 +64,17 @@ if (process.env.NODE_ENV === 'test') {
|
||||
srf.use('invite', [initLocals, challengeDeviceCalls]);
|
||||
|
||||
srf.invite((req, res) => {
|
||||
if (req.has('Replaces')) {
|
||||
const arr = /^(.*);from/.exec(req.get('Replaces'));
|
||||
if (arr) logger.info(`replacing call-id ${arr}`);
|
||||
else logger.info(`failed parsing ${req.get('Replaces')}`);
|
||||
const session = arr ? activeCallIds.get(arr[1]) : null;
|
||||
if (!session) {
|
||||
logger.info(`failed to find session in Replaces header: ${req.has('Replaces')}`);
|
||||
return res.send(404);
|
||||
}
|
||||
return session.replaces(req, res);
|
||||
}
|
||||
const session = new CallSession(logger, req, res);
|
||||
session.connect();
|
||||
});
|
||||
@@ -71,6 +86,6 @@ srf.use((req, res, next, err) => {
|
||||
|
||||
setInterval(() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
||||
}, 5000);
|
||||
}, 20000);
|
||||
|
||||
module.exports = {srf, logger};
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"DTLS": "off",
|
||||
"SDES": "off",
|
||||
"ICE": "remove",
|
||||
"rtcp-mux": ["demux"]
|
||||
"flags": ["media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
{
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no"],
|
||||
"rtcp-mux": ["require"]
|
||||
"default": {
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["require"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover"],
|
||||
"rtcp-mux": ["accept"]
|
||||
}
|
||||
}
|
||||
+251
-20
@@ -1,8 +1,21 @@
|
||||
const Emitter = require('events');
|
||||
const {isWSS, makeRtpEngineOpts} = require('./utils');
|
||||
const {makeRtpEngineOpts, SdpWantsSrtp} = require('./utils');
|
||||
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
|
||||
const {parseUri, SipError} = require('drachtio-srf');
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const MS_TEAMS_USER_AGENT = 'Microsoft.PSTNHub.SIPProxy';
|
||||
const MS_TEAMS_SIP_ENDPOINT = 'sip.pstnhub.microsoft.com';
|
||||
|
||||
/**
|
||||
* this is to make sure the outgoing From has the number in the incoming From
|
||||
* and not the incoming PAI
|
||||
*/
|
||||
const createBLegFromHeader = (req) => {
|
||||
const from = req.getParsedHeader('From');
|
||||
const uri = parseUri(from.uri);
|
||||
if (uri && uri.user) return `sip:${uri.user}@localhost`;
|
||||
return 'sip:anonymous@localhost';
|
||||
};
|
||||
|
||||
class CallSession extends Emitter {
|
||||
constructor(logger, req, res) {
|
||||
@@ -16,7 +29,10 @@ class CallSession extends Emitter {
|
||||
this.getFeatureServer = req.srf.locals.getFeatureServer;
|
||||
this.stats = this.srf.locals.stats;
|
||||
this.activeCallIds = this.srf.locals.activeCallIds;
|
||||
}
|
||||
|
||||
get isFromMSTeams() {
|
||||
return !!this.req.locals.msTeamsTenantFqdn;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
@@ -43,22 +59,24 @@ class CallSession extends Emitter {
|
||||
}
|
||||
debug(`using feature server ${featureServer}`);
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, isWSS(this.req), false);
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams);
|
||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||
const obj = parseUri(this.req.uri);
|
||||
let proxy, host, uri;
|
||||
|
||||
// replace host part of uri if its an ipv4 address, leave it otherwise
|
||||
if (/\d{1-3}\.\d{1-3}\.\d{1-3}\.\d{1-3}/.test(obj.host)) {
|
||||
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(obj.host)) {
|
||||
debug(`replacing host: was ${obj.host} is ${featureServer}`);
|
||||
host = featureServer;
|
||||
}
|
||||
else {
|
||||
debug(`not replacing host: "${obj.host}"`);
|
||||
host = obj.host;
|
||||
proxy = `sip:${featureServer}`;
|
||||
}
|
||||
else {
|
||||
host = featureServer;
|
||||
}
|
||||
if (obj.user) uri = `${obj.scheme}:${obj.user}@${host}`;
|
||||
else uri = `${obj.scheme}:${host}`;
|
||||
debug(`uri will be: ${uri}, proxy ${proxy}`);
|
||||
this.logger.info(`uri will be: ${uri}, proxy ${proxy}`);
|
||||
|
||||
try {
|
||||
const response = await this.offer(this.rtpEngineOpts.offer);
|
||||
@@ -70,10 +88,22 @@ class CallSession extends Emitter {
|
||||
|
||||
// now send the INVITE in towards the feature servers
|
||||
const headers = {
|
||||
'From': createBLegFromHeader(this.req),
|
||||
'To': this.req.get('To'),
|
||||
'X-CID': this.req.get('Call-ID'),
|
||||
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
|
||||
};
|
||||
const responseHeaders = {};
|
||||
if (this.req.locals.carrier) Object.assign(headers, {'X-Originating-Carrier': this.req.locals.carrier});
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
Object.assign(headers, {'X-MS-Teams-Tenant-FQDN': this.req.locals.msTeamsTenantFqdn});
|
||||
|
||||
// for Microsoft Teams the Contact header must include the tenant FQDN
|
||||
Object.assign(responseHeaders, {
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY, UPDATE, PRACK',
|
||||
Contact: `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
});
|
||||
}
|
||||
if (this.req.locals.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.locals.application_sid});
|
||||
}
|
||||
@@ -81,7 +111,7 @@ class CallSession extends Emitter {
|
||||
if (this.req.authorization.grant && this.req.authorization.grant.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.authorization.grant.application_sid});
|
||||
}
|
||||
else if (this.req.authorization.challengeResponse) {
|
||||
if (this.req.authorization.challengeResponse) {
|
||||
const {username, realm} = this.req.authorization.challengeResponse;
|
||||
Object.assign(headers, {'X-Authenticated-User': `${username}@${realm}`});
|
||||
}
|
||||
@@ -93,12 +123,13 @@ class CallSession extends Emitter {
|
||||
const {uas, uac} = await this.srf.createB2BUA(this.req, this.res, uri, {
|
||||
proxy,
|
||||
headers,
|
||||
proxyRequestHeaders: ['all', '-Authorization', '-Max-Forwards'],
|
||||
responseHeaders,
|
||||
proxyRequestHeaders: ['all', '-Authorization', '-Max-Forwards', '-Record-Route'],
|
||||
proxyResponseHeaders: ['all'],
|
||||
localSdpB: response.sdp,
|
||||
localSdpA: async(sdp, res) => {
|
||||
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
this.toTag = res.getParsedHeader('To').params.tag;
|
||||
const opts = Object.assign({sdp, 'to-tag': this.toTag}, this.rtpEngineOpts.answer);
|
||||
const response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
this.logger.error(`rtpengine answer failed with ${JSON.stringify(response)}`);
|
||||
@@ -110,6 +141,7 @@ class CallSession extends Emitter {
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('call connected successfully to feature server');
|
||||
this.toTag = uas.sip.localTag;
|
||||
this._setHandlers({uas, uac});
|
||||
return;
|
||||
} catch (err) {
|
||||
@@ -128,11 +160,23 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
_setDlgHandlers(dlg) {
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
dlg.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
//re-invite
|
||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||
}
|
||||
|
||||
_setHandlers({uas, uac}) {
|
||||
this.emit('connected');
|
||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
this.activeCallIds.add(this.req.get('Call-ID'));
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
|
||||
this.uas = uas;
|
||||
this.uac = uac;
|
||||
@@ -140,39 +184,226 @@ class CallSession extends Emitter {
|
||||
//hangup
|
||||
dlg.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy();
|
||||
this.rtpEngineResource.destroy().catch((err) => {});
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
dlg.other.destroy().catch((e) => {});
|
||||
});
|
||||
|
||||
//re-invite
|
||||
dlg.on('modify', this._onReinvite.bind(this, dlg));
|
||||
});
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
|
||||
uac.on('modify', this._onFeatureServerReinvite.bind(this, uac));
|
||||
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
||||
uas.on('refer', this._onRefer.bind(this, uas));
|
||||
|
||||
// default forwarding of other request types
|
||||
forwardInDialogRequests(uas);
|
||||
forwardInDialogRequests(uas, ['info', 'notify', 'options', 'message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* handle INVITE with Replaces header from uas side (this will never come from the feature server)
|
||||
* @param {*} req incoming request
|
||||
* @param {*} res incoming response
|
||||
*/
|
||||
async replaces(req, res) {
|
||||
try {
|
||||
let opts = Object.assign(this.rtpEngineOpts.offer, {sdp: req.body});
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`replaces: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent offer for reinvite to rtpengine');
|
||||
const sdp = await this.uac.modify(response.sdp);
|
||||
opts = Object.assign(this.rtpEngineOpts.answer, {sdp, 'to-tag': this.toTag});
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`replaces: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent answer for reinvite to rtpengine');
|
||||
const headers = {};
|
||||
if (this.req.locals.msTeamsTenantFqdn) {
|
||||
Object.assign(headers, {'X-MS-Teams-Tenant-FQDN': this.req.locals.msTeamsTenantFqdn});
|
||||
|
||||
// for Microsoft Teams the Contact header must include the tenant FQDN
|
||||
Object.assign(headers, {
|
||||
Allow: 'INVITE, ACK, OPTIONS, CANCEL, BYE, NOTIFY',
|
||||
Contact: `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
});
|
||||
}
|
||||
|
||||
const uas = await this.srf.createUAS(req, res, {
|
||||
localSdp: response.sdp,
|
||||
headers
|
||||
});
|
||||
this.logger.info('successfully connected new INVITE w/replaces, hanging up leg being replaced');
|
||||
this.uas.destroy();
|
||||
this.req = req;
|
||||
this.uas = uas;
|
||||
this.uas.other = this.uac;
|
||||
this.uac.other = this.uas;
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this._setDlgHandlers(uas);
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling invite with replaces');
|
||||
res.send(err.status || 500);
|
||||
}
|
||||
}
|
||||
|
||||
async _onReinvite(dlg, req, res) {
|
||||
try {
|
||||
let response = await this.offer(Object.assign({sdp: req.body}, this.rtpEngineOpts.offer));
|
||||
let opts = Object.assign(this.rtpEngineOpts.offer, {sdp: req.body});
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: offer: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent offer for reinvite to rtpengine');
|
||||
const sdp = await dlg.other.modify(response.sdp);
|
||||
const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
opts = Object.assign({sdp, 'to-tag': this.toTag}, this.rtpEngineOpts.answer);
|
||||
response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info({opts, response}, 'sent answer for reinvite to rtpengine');
|
||||
res.send(200, {body: response.sdp});
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
}
|
||||
}
|
||||
|
||||
async _onFeatureServerReinvite(dlg, req, res) {
|
||||
try {
|
||||
const opts = Object.assign({sdp: req.body, 'to-tag': this.toTag}, this.rtpEngineOpts.answer);
|
||||
const response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onFeatureServerReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
res.send(200, {body: dlg.local.sdp});
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite from feature server');
|
||||
}
|
||||
}
|
||||
|
||||
async _onFeatureServerTransfer(dlg, req, res) {
|
||||
try {
|
||||
const referTo = req.getParsedHeader('Refer-To');
|
||||
const uri = parseUri(referTo.uri);
|
||||
this.logger.info({uri, referTo}, 'received REFER from feature server');
|
||||
const arr = /context-(.*)/.exec(uri.user);
|
||||
if (!arr) {
|
||||
this.logger.info(`invalid Refer-To header: ${referTo.uri}`);
|
||||
return res.send(501);
|
||||
}
|
||||
res.send(202);
|
||||
|
||||
// invite to new fs
|
||||
const headers = {};
|
||||
if (req.has('X-Retain-Call-Sid')) {
|
||||
Object.assign(headers, {'X-Retain-Call-Sid': req.get('X-Retain-Call-Sid')});
|
||||
}
|
||||
const uac = await this.srf.createUAC(referTo.uri, {localSdp: dlg.local.sdp, headers});
|
||||
this.uac = uac;
|
||||
uac.other = this.uas;
|
||||
this.uas.other = uac;
|
||||
uac.on('modify', this._onFeatureServerReinvite.bind(this, uac));
|
||||
uac.on('refer', this._onFeatureServerTransfer.bind(this, uac));
|
||||
uac.on('destroy', () => {
|
||||
this.logger.info('call ended with normal termination');
|
||||
this.rtpEngineResource.destroy();
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
uac.other.destroy();
|
||||
});
|
||||
// now we can destroy the old dialog
|
||||
dlg.destroy().catch(() => {});
|
||||
|
||||
// modify rtpengine to stream to new feature server
|
||||
const opts = Object.assign({sdp: uac.remote.sdp, 'to-tag': res.getParsedHeader('To').params.tag},
|
||||
this.rtpEngineOpts.answer);
|
||||
const response = await this.answer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
res.send(488);
|
||||
throw new Error(`_onFeatureServerReinvite: rtpengine failed: ${JSON.stringify(response)}`);
|
||||
}
|
||||
this.logger.info('successfully moved call to new feature server');
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling refer from feature server');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async _onRefer(dlg, req, res) {
|
||||
const ua = req.get('User-Agent');
|
||||
const referTo = req.get('Refer-To');
|
||||
const rt = req.getParsedHeader('Refer-To');
|
||||
const uri = parseUri(rt.uri);
|
||||
this.logger.info({referTo, ua, rt, uri}, 'got a REFER');
|
||||
|
||||
/**
|
||||
* send NOTIFY of INVITE status, return true if call answered
|
||||
*/
|
||||
const sendNotify = (dlg, body) => {
|
||||
const arr = /SIP\/2.0\s+(\d+).*$/.exec(body);
|
||||
const status = arr ? parseInt(arr[1]) : null;
|
||||
dlg.request({
|
||||
method: 'NOTIFY',
|
||||
headers: {
|
||||
'Content-Type': 'message/sipfrag;version=2.0',
|
||||
'Contact': `sip:${this.req.locals.msTeamsTenantFqdn}`
|
||||
},
|
||||
body
|
||||
});
|
||||
this.logger.info(`sent NOTIFY for REFER with status ${status}`);
|
||||
return status === 200;
|
||||
};
|
||||
|
||||
if (this.isFromMSTeams && ua.startsWith(MS_TEAMS_USER_AGENT) &&
|
||||
referTo.startsWith(`<sip:${MS_TEAMS_SIP_ENDPOINT}`) &&
|
||||
!uri.user) {
|
||||
|
||||
// the Refer-To endpoint is within Teams itself, so we can handle
|
||||
res.send(202);
|
||||
try {
|
||||
const dlg = await this.srf.createUAC(rt.uri, {
|
||||
localSdp: this.uas.local.sdp.replace(/a=inactive/g, 'a=sendrecv'),
|
||||
headers: {
|
||||
'From': `sip:${this.req.callingNumber}@${this.req.locals.msTeamsTenantFqdn}`,
|
||||
'Contact': `sip:${this.req.callingNumber}@${this.req.locals.msTeamsTenantFqdn}`
|
||||
}
|
||||
},
|
||||
{
|
||||
cbRequest: (err, inviteSent) => {
|
||||
if (err) return sendNotify(this.uas, `SIP/2.0 ${err.status || '500'}`);
|
||||
sendNotify(this.uas, '100 Trying ');
|
||||
this.referInvite = inviteSent;
|
||||
},
|
||||
cbProvisional: (prov) => {
|
||||
sendNotify(this.uas, `${prov.status} ${prov.reason}`);
|
||||
}
|
||||
});
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('successfully connected new call leg for REFER');
|
||||
this.referInvite = null;
|
||||
sendNotify(this.uas, '200 OK');
|
||||
this.uas.destroy();
|
||||
this.uas = dlg;
|
||||
this.uas.other = this.uac;
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this._setDlgHandlers(dlg);
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'Error creating new call leg for REFER');
|
||||
sendNotify(this.uas, `${err.status || 500} ${err.reason || ''}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: forward on to feature server
|
||||
res.send(501);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CallSession;
|
||||
|
||||
+27
-8
@@ -7,6 +7,12 @@ module.exports = (srf, logger) => {
|
||||
let dynamic = true;
|
||||
let idx = 0;
|
||||
const stats = srf.locals.stats;
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
|
||||
|
||||
const {createSet} = require('jambonz-realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
srf.options((req, res) => {
|
||||
res.send(200);
|
||||
@@ -15,24 +21,37 @@ module.exports = (srf, logger) => {
|
||||
const status = req.get('X-FS-Status');
|
||||
const calls = req.has('X-FS-Calls') ? parseInt(req.get('X-FS-Calls')) : 0;
|
||||
if (status === 'open') {
|
||||
if (!contacts.has(uri)) {
|
||||
logger.info(`adding feature server at ${uri}`);
|
||||
stats.gauge('sbc.featureservers.count', contacts.size + 1);
|
||||
}
|
||||
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.includes(uri)) {
|
||||
logger.info(`removing feature server at ${uri}`);
|
||||
if (contacts.has(uri)) {
|
||||
contacts.delete(uri);
|
||||
stats.gauge('sbc.featureservers.count', contacts.size + 1);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.JAMBONES_FEATURE_SERVERS) {
|
||||
// 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(',')
|
||||
|
||||
+18
-9
@@ -1,5 +1,7 @@
|
||||
const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const Emitter = require('events');
|
||||
const parseUri = require('drachtio-srf').parseUri;
|
||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ? process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) : [];
|
||||
|
||||
class AuthOutcomeReporter extends Emitter {
|
||||
constructor(stats) {
|
||||
@@ -35,16 +37,23 @@ module.exports = function(srf, logger) {
|
||||
async function challengeDeviceCalls(req, res, next) {
|
||||
try {
|
||||
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
|
||||
if (!gateway) {
|
||||
// TODO: if uri.host is not a domain, just reject
|
||||
req.locals.originator = 'device';
|
||||
return authenticator(req, res, next);
|
||||
if (gateway) {
|
||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
||||
req.locals.originator = 'trunk';
|
||||
req.locals.carrier = gateway.name;
|
||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
||||
return next();
|
||||
}
|
||||
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`);
|
||||
req.locals.originator = 'trunk';
|
||||
req.locals.carrier = gateway.name;
|
||||
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid;
|
||||
next();
|
||||
if (msProxyIps.includes(req.source_address)) {
|
||||
logger.debug({source_address: req.source_address}, 'challengeDeviceCalls: incoming call from Microsoft Teams');
|
||||
const uri = parseUri(req.uri);
|
||||
req.locals.originator = 'teams';
|
||||
req.locals.carrier = 'Microsoft Teams';
|
||||
req.locals.msTeamsTenantFqdn = uri.host;
|
||||
return next();
|
||||
}
|
||||
req.locals.originator = 'device';
|
||||
return authenticator(req, res, next);
|
||||
} catch (err) {
|
||||
stats.increment('sbc.terminations', ['sipStatus:500']);
|
||||
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
|
||||
|
||||
+15
-4
@@ -11,19 +11,30 @@ function getAppserver(srf) {
|
||||
return srf.locals.featureServers[ idx++ % len];
|
||||
}
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) {
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
const srtpOpts = teams ? srtpCharacteristics['teams'] : srtpCharacteristics['default'];
|
||||
const from = req.getParsedHeader('from');
|
||||
const common = {'call-id': req.get('Call-ID'), 'from-tag': from.params.tag};
|
||||
return {
|
||||
common,
|
||||
offer: Object.assign({'sdp': req.body, 'replace': ['origin', 'session-connection']}, common,
|
||||
dstIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics),
|
||||
answer: Object.assign({}, common, srcIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics)
|
||||
offer: Object.assign(
|
||||
{'sdp': req.body, 'replace': ['origin', 'session-connection']},
|
||||
common,
|
||||
dstIsUsingSrtp ? srtpOpts : rtpCharacteristics),
|
||||
answer: Object.assign(
|
||||
{'replace': ['origin', 'session-connection']},
|
||||
common,
|
||||
srcIsUsingSrtp ? srtpOpts : rtpCharacteristics)
|
||||
};
|
||||
}
|
||||
|
||||
function SdpWantsSrtp(sdp) {
|
||||
return /m=audio.*SAVP/.test(sdp);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isWSS,
|
||||
SdpWantsSrtp,
|
||||
getAppserver,
|
||||
makeRtpEngineOpts
|
||||
};
|
||||
|
||||
Generated
+1497
-2981
File diff suppressed because it is too large
Load Diff
+8
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.2",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
@@ -27,9 +27,10 @@
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.4.28",
|
||||
"jambonz-db-helpers": "^0.3.3",
|
||||
"drachtio-srf": "^4.4.33",
|
||||
"@jambonz/db-helpers": "^0.3.6",
|
||||
"jambonz-http-authenticator": "0.1.5",
|
||||
"jambonz-realtimedb-helpers": "^0.2.4",
|
||||
"jambonz-rtpengine-utils": "0.1.1",
|
||||
"jambonz-stats-collector": "0.0.3",
|
||||
"pino": "^5.14.0",
|
||||
@@ -37,13 +38,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"blue-tape": "^1.0.0",
|
||||
"clear-module": "^4.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"clear-module": "^4.1.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"nyc": "^14.1.1",
|
||||
"tap": "^14.10.2",
|
||||
"tap-dot": "^2.0.0",
|
||||
"tap-spec": "^5.0.0",
|
||||
"tape": "^4.11.0"
|
||||
"nyc": "^15.0.1",
|
||||
"tap-spec": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+189
-137
@@ -2,203 +2,255 @@
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `call_routes`;
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_carrier_set_entry`;
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS `lcr_routes`;
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS `api_keys`;
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS `phone_numbers`;
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
||||
DROP TABLE IF EXISTS `sip_gateways`;
|
||||
DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS `voip_carriers`;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS `accounts`;
|
||||
DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS `applications`;
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS `service_providers`;
|
||||
DROP TABLE IF EXISTS voip_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS `webhooks`;
|
||||
DROP TABLE IF EXISTS applications;
|
||||
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
|
||||
DROP TABLE IF EXISTS service_providers;
|
||||
|
||||
DROP TABLE IF EXISTS webhooks;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `call_routes`
|
||||
CREATE TABLE call_routes
|
||||
(
|
||||
`call_route_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`priority` INTEGER NOT NULL,
|
||||
`account_sid` CHAR(36) NOT NULL,
|
||||
`regex` VARCHAR(255) NOT NULL,
|
||||
`application_sid` CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (`call_route_sid`)
|
||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
priority INTEGER NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(255) NOT NULL,
|
||||
application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `lcr_routes`
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
`lcr_route_sid` CHAR(36),
|
||||
`regex` VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
`description` VARCHAR(1024),
|
||||
`priority` INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (`lcr_route_sid`)
|
||||
lcr_route_sid CHAR(36),
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `api_keys`
|
||||
CREATE TABLE ms_teams_tenants
|
||||
(
|
||||
`api_key_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`token` CHAR(36) NOT NULL UNIQUE ,
|
||||
`account_sid` CHAR(36),
|
||||
`service_provider_sid` CHAR(36),
|
||||
PRIMARY KEY (`api_key_sid`)
|
||||
ms_teams_tenant_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
CREATE TABLE api_keys
|
||||
(
|
||||
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
token CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36),
|
||||
service_provider_sid CHAR(36),
|
||||
expires_at TIMESTAMP,
|
||||
PRIMARY KEY (api_key_sid)
|
||||
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `voip_carriers`
|
||||
CREATE TABLE sbc_addresses
|
||||
(
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`description` VARCHAR(255),
|
||||
`account_sid` CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
|
||||
`application_sid` CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
|
||||
PRIMARY KEY (`voip_carrier_sid`)
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
service_provider_sid CHAR(36),
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name CHAR(36) NOT NULL UNIQUE ,
|
||||
hashed_password VARCHAR(1024) NOT NULL,
|
||||
salt CHAR(16) NOT NULL,
|
||||
force_change BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE voip_carriers
|
||||
(
|
||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account',
|
||||
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `phone_numbers`
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
`phone_number_sid` CHAR(36) UNIQUE ,
|
||||
`number` VARCHAR(32) NOT NULL UNIQUE ,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`account_sid` CHAR(36),
|
||||
`application_sid` CHAR(36),
|
||||
PRIMARY KEY (`phone_number_sid`)
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
PRIMARY KEY (phone_number_sid)
|
||||
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `webhooks`
|
||||
CREATE TABLE webhooks
|
||||
(
|
||||
`webhook_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`url` VARCHAR(1024) NOT NULL,
|
||||
`method` ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
`username` VARCHAR(255),
|
||||
`password` VARCHAR(255),
|
||||
PRIMARY KEY (`webhook_sid`)
|
||||
webhook_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `lcr_carrier_set_entry`
|
||||
CREATE TABLE sip_gateways
|
||||
(
|
||||
`lcr_carrier_set_entry_sid` CHAR(36),
|
||||
`workload` INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
`lcr_route_sid` CHAR(36) NOT NULL,
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`priority` INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (`lcr_carrier_set_entry_sid`)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sip_gateways`
|
||||
(
|
||||
`sip_gateway_sid` CHAR(36),
|
||||
`ipv4` VARCHAR(32) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
`port` INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
`inbound` BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
`outbound` BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
`voip_carrier_sid` CHAR(36) NOT NULL,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`sip_gateway_sid`)
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `applications`
|
||||
CREATE TABLE lcr_carrier_set_entry
|
||||
(
|
||||
`application_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL,
|
||||
`account_sid` CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
|
||||
`call_hook_sid` CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
|
||||
`call_status_hook_sid` CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
`speech_synthesis_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_synthesis_voice` VARCHAR(64) NOT NULL DEFAULT 'en-US-Wavenet-C',
|
||||
`speech_recognizer_vendor` VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
`speech_recognizer_language` VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
PRIMARY KEY (`application_sid`)
|
||||
lcr_carrier_set_entry_sid CHAR(36),
|
||||
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
lcr_route_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE applications
|
||||
(
|
||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to',
|
||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls to phone numbers owned by this account',
|
||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
PRIMARY KEY (application_sid)
|
||||
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `service_providers`
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
`service_provider_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL UNIQUE ,
|
||||
`description` VARCHAR(255),
|
||||
`root_domain` VARCHAR(128) UNIQUE ,
|
||||
`registration_hook_sid` CHAR(36),
|
||||
PRIMARY KEY (`service_provider_sid`)
|
||||
service_provider_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
root_domain VARCHAR(128) UNIQUE ,
|
||||
registration_hook_sid CHAR(36),
|
||||
ms_teams_fqdn VARCHAR(255),
|
||||
PRIMARY KEY (service_provider_sid)
|
||||
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `accounts`
|
||||
CREATE TABLE accounts
|
||||
(
|
||||
`account_sid` CHAR(36) NOT NULL UNIQUE ,
|
||||
`name` VARCHAR(64) NOT NULL,
|
||||
`sip_realm` VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
|
||||
`service_provider_sid` CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
|
||||
`registration_hook_sid` CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
|
||||
`device_calling_application_sid` CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (`account_sid`)
|
||||
account_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
|
||||
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
|
||||
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
|
||||
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (account_sid)
|
||||
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
CREATE INDEX `call_routes_call_route_sid_idx` ON `call_routes` (`call_route_sid`);
|
||||
ALTER TABLE `call_routes` ADD FOREIGN KEY account_sid_idxfk (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE `call_routes` ADD FOREIGN KEY application_sid_idxfk (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `api_keys_api_key_sid_idx` ON `api_keys` (`api_key_sid`);
|
||||
CREATE INDEX `api_keys_account_sid_idx` ON `api_keys` (`account_sid`);
|
||||
ALTER TABLE `api_keys` ADD FOREIGN KEY account_sid_idxfk_1 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX 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 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX `api_keys_service_provider_sid_idx` ON `api_keys` (`service_provider_sid`);
|
||||
ALTER TABLE `api_keys` ADD FOREIGN KEY service_provider_sid_idxfk (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX `voip_carriers_voip_carrier_sid_idx` ON `voip_carriers` (`voip_carrier_sid`);
|
||||
CREATE INDEX `voip_carriers_name_idx` ON `voip_carriers` (`name`);
|
||||
ALTER TABLE `voip_carriers` ADD FOREIGN KEY account_sid_idxfk_2 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
ALTER TABLE `voip_carriers` 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 api_key_sid_idx ON api_keys (api_key_sid);
|
||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX `phone_numbers_phone_number_sid_idx` ON `phone_numbers` (`phone_number_sid`);
|
||||
CREATE INDEX `phone_numbers_voip_carrier_sid_idx` ON `phone_numbers` (`voip_carrier_sid`);
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY voip_carrier_sid_idxfk (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY account_sid_idxfk_3 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||
|
||||
ALTER TABLE `phone_numbers` ADD FOREIGN KEY application_sid_idxfk_2 (`application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
|
||||
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX `webhooks_webhook_sid_idx` ON `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY lcr_route_sid_idxfk (`lcr_route_sid`) REFERENCES `lcr_routes` (`lcr_route_sid`);
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX name_idx ON users (name);
|
||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX name_idx ON voip_carriers (name);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE `lcr_carrier_set_entry` ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE UNIQUE INDEX `sip_gateways_sip_gateway_idx_hostport` ON `sip_gateways` (`ipv4`,`port`);
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE `sip_gateways` ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (`voip_carrier_sid`) REFERENCES `voip_carriers` (`voip_carrier_sid`);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE UNIQUE INDEX `applications_idx_name` ON `applications` (`account_sid`,`name`);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX `applications_application_sid_idx` ON `applications` (`application_sid`);
|
||||
CREATE INDEX `applications_name_idx` ON `applications` (`name`);
|
||||
CREATE INDEX `applications_account_sid_idx` ON `applications` (`account_sid`);
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY account_sid_idxfk_4 (`account_sid`) REFERENCES `accounts` (`account_sid`);
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_hook_sid_idxfk (`call_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE `applications` ADD FOREIGN KEY call_status_hook_sid_idxfk (`call_status_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
|
||||
|
||||
CREATE INDEX `service_providers_service_provider_sid_idx` ON `service_providers` (`service_provider_sid`);
|
||||
CREATE INDEX `service_providers_name_idx` ON `service_providers` (`name`);
|
||||
CREATE INDEX `service_providers_root_domain_idx` ON `service_providers` (`root_domain`);
|
||||
ALTER TABLE `service_providers` ADD FOREIGN KEY registration_hook_sid_idxfk (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE INDEX `accounts_account_sid_idx` ON `accounts` (`account_sid`);
|
||||
CREATE INDEX `accounts_name_idx` ON `accounts` (`name`);
|
||||
CREATE INDEX `accounts_sip_realm_idx` ON `accounts` (`sip_realm`);
|
||||
CREATE INDEX `accounts_service_provider_sid_idx` ON `accounts` (`service_provider_sid`);
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY service_provider_sid_idxfk_1 (`service_provider_sid`) REFERENCES `service_providers` (`service_provider_sid`);
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY registration_hook_sid_idxfk_1 (`registration_hook_sid`) REFERENCES `webhooks` (`webhook_sid`);
|
||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE `accounts` ADD FOREIGN KEY device_calling_application_sid_idxfk (`device_calling_application_sid`) REFERENCES `applications` (`application_sid`);
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_status_hook_sid_idxfk (call_status_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON service_providers (service_provider_sid);
|
||||
CREATE INDEX name_idx ON service_providers (name);
|
||||
CREATE INDEX root_domain_idx ON service_providers (root_domain);
|
||||
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
Reference in New Issue
Block a user