mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eac1d7a9e9 | ||
|
|
fae1732ddf | ||
|
|
c858624815 | ||
|
|
2f137f2bfc | ||
|
|
ef0dcfc021 | ||
|
|
fb8baa34af | ||
|
|
7efce18a03 | ||
|
|
84e5202607 | ||
|
|
8f856a30a6 | ||
|
|
d9881632c2 | ||
|
|
6d2b1a94e9 | ||
|
|
ded83ac39a | ||
|
|
91a29d2b2c | ||
|
|
27686a80e8 | ||
|
|
a9e79b23bd | ||
|
|
faafaa8549 | ||
|
|
b24ecf84d3 | ||
|
|
3360ddb3e6 | ||
|
|
b2d48c8954 | ||
|
|
1f1a4d2330 | ||
|
|
c34da3cfa8 | ||
|
|
5b88065925 | ||
|
|
438924ca36 | ||
|
|
c4d4d7bc0a | ||
|
|
ebcd0ce4a2 | ||
|
|
cf239ca7fa | ||
|
|
b072524585 | ||
|
|
7fb9966d20 | ||
|
|
42b3317206 | ||
|
|
db66d98d0a | ||
|
|
26169517dc |
@@ -20,7 +20,7 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14.x
|
||||
node-version: 18.x
|
||||
- run: npm ci
|
||||
- run: npm run jslint
|
||||
- run: npm test
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.9.0-alpine3.16 as base
|
||||
FROM --platform=linux/amd64 node:18.12.1-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ const {
|
||||
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
|
||||
});
|
||||
const StatsCollector = require('@jambonz/stats-collector');
|
||||
const CIDRMatcher = require('cidr-matcher');
|
||||
const stats = new StatsCollector(logger);
|
||||
const {equalsIgnoreOrder, createHealthCheckApp, systemHealth} = require('./lib/utils');
|
||||
const {LifeCycleEvents} = require('./lib/constants');
|
||||
@@ -129,7 +130,6 @@ const {
|
||||
const CallSession = require('./lib/call-session');
|
||||
|
||||
if (process.env.DRACHTIO_HOST && !process.env.K8S) {
|
||||
const CIDRMatcher = require('cidr-matcher');
|
||||
const cidrs = process.env.JAMBONES_NETWORK_CIDR
|
||||
.split(',')
|
||||
.map((s) => s.trim());
|
||||
@@ -164,6 +164,23 @@ else {
|
||||
logger.info(`listening in outbound mode on port ${process.env.DRACHTIO_PORT}`);
|
||||
});
|
||||
srf.listen({port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET});
|
||||
srf.on('connect', (err, hp) => {
|
||||
if (err) return this.logger.error({err}, 'Error connecting to drachtio server');
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
|
||||
if (process.env.K8S_FEATURE_SERVER_TRANSPORT === 'tcp') {
|
||||
const matcher = new CIDRMatcher(['192.168.0.0/24', '172.16.0.0/16', '10.0.0.0/8']);
|
||||
const hostports = hp.split(',');
|
||||
for (const hp of hostports) {
|
||||
const arr = /^(.*)\/(.*):(\d+)$/.exec(hp);
|
||||
if (arr && matcher.contains(arr[2])) {
|
||||
const hostport = `${arr[2]}:${arr[3]}`;
|
||||
logger.info(`using sbc private address when sending to feature-server: ${hostport}`);
|
||||
srf.locals.privateSipAddress = hostport;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
srf.on('error', (err) => {
|
||||
@@ -235,7 +252,6 @@ if ('test' !== process.env.NODE_ENV) {
|
||||
}
|
||||
|
||||
const lookupRtpServiceEndpoints = (lookup, serviceName) => {
|
||||
logger.debug(`dns lookup for ${serviceName}..`);
|
||||
lookup(serviceName, {family: 4, all: true}, (err, addresses) => {
|
||||
if (err) {
|
||||
logger.error({err}, `Error looking up ${serviceName}`);
|
||||
@@ -305,8 +321,18 @@ process.on('SIGUSR2', handle.bind(null, removeFromSet, setName));
|
||||
process.on('SIGTERM', handle.bind(null, removeFromSet, setName));
|
||||
|
||||
function handle(removeFromSet, setName, signal) {
|
||||
logger.info(`got signal ${signal}, removing ${srf.locals.privateSipAddress} from set ${setName}`);
|
||||
removeFromSet(setName, srf.locals.privateSipAddress);
|
||||
logger.info(`got signal ${signal}`);
|
||||
if (srf.locals.privateSipAddress && setName) {
|
||||
logger.info(`removing ${srf.locals.privateSipAddress} from set ${setName}`);
|
||||
removeFromSet(setName, srf.locals.privateSipAddress);
|
||||
}
|
||||
if (process.env.K8S) {
|
||||
lifecycleEmitter.operationalState = LifeCycleEvents.ScaleIn;
|
||||
if (0 === activeCallIds.size) {
|
||||
logger.info('exiting immediately since we have no calls in progress');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {srf, logger};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"default": {
|
||||
"transport-protocol": "UDP/TLS/RTP/SAVPF",
|
||||
"ICE": "force",
|
||||
"ICE": "default",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||
"rtcp-mux": ["require"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
"ICE": "force",
|
||||
"ICE": "default",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "media handover", "port latching"],
|
||||
"rtcp-mux": ["accept"]
|
||||
|
||||
@@ -57,6 +57,9 @@ module.exports = (logger) => {
|
||||
}
|
||||
})();
|
||||
}
|
||||
else if (process.env.K8S) {
|
||||
lifecycleEmitter.scaleIn = () => process.exit(0);
|
||||
}
|
||||
|
||||
return {lifecycleEmitter};
|
||||
};
|
||||
|
||||
+54
-29
@@ -3,6 +3,7 @@ const SrsClient = require('@jambonz/siprec-client-utils');
|
||||
const {
|
||||
makeRtpEngineOpts,
|
||||
SdpWantsSrtp,
|
||||
SdpWantsSDES,
|
||||
nudgeCallCounts,
|
||||
roundTripTime,
|
||||
parseConnectionIp
|
||||
@@ -84,6 +85,20 @@ class CallSession extends Emitter {
|
||||
return this.req.has('X-Five9-StreamingPairId');
|
||||
}
|
||||
|
||||
subscribeForDTMF(dlg) {
|
||||
if (!this._subscribedForDTMF) {
|
||||
this._subscribedForDTMF = true;
|
||||
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag,
|
||||
this._onDTMF.bind(this, dlg));
|
||||
}
|
||||
}
|
||||
unsubscribeForDTMF() {
|
||||
if (this._subscribedForDTMF) {
|
||||
this._subscribedForDTMF = false;
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
const {sdp} = this.req.locals;
|
||||
this.logger.info('inbound call accepted for routing');
|
||||
@@ -133,7 +148,9 @@ class CallSession extends Emitter {
|
||||
}
|
||||
this.logger.debug(`using feature server ${featureServer}`);
|
||||
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(sdp), false, this.isFromMSTeams);
|
||||
const wantsSrtp = this.req.locals.possibleWebRtcClient = SdpWantsSrtp(sdp);
|
||||
const wantsSDES = SdpWantsSDES(sdp);
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, wantsSrtp, false, this.isFromMSTeams || wantsSDES);
|
||||
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
|
||||
const obj = parseUri(this.req.uri);
|
||||
let proxy, host, uri;
|
||||
@@ -243,6 +260,7 @@ class CallSession extends Emitter {
|
||||
};
|
||||
const startAt = process.hrtime();
|
||||
const response = await this.answer(opts);
|
||||
this.logger.debug({response, opts}, 'response from rtpengine to answer');
|
||||
const rtt = roundTripTime(startAt);
|
||||
this.stats.histogram('app.rtpengine.response_time', rtt, [
|
||||
'direction:inbound', 'command:answer', `rtpengine:${this.rtpengineIp}`]);
|
||||
@@ -290,8 +308,7 @@ class CallSession extends Emitter {
|
||||
_setDlgHandlers(dlg) {
|
||||
const {callId} = dlg.sip;
|
||||
this.activeCallIds.set(callId, this);
|
||||
this.subscribeDTMF(this.logger, callId, this.rtpEngineOpts.uas.tag,
|
||||
this._onDTMF.bind(this));
|
||||
this.subscribeForDTMF(this.uac);
|
||||
dlg.on('destroy', () => {
|
||||
debug('call ended with normal termination');
|
||||
this.logger.info('call ended with normal termination');
|
||||
@@ -314,11 +331,11 @@ class CallSession extends Emitter {
|
||||
_setHandlers({uas, uac}) {
|
||||
this.emit('connected');
|
||||
const callStart = Date.now();
|
||||
const call_sid = uac.res?.get('X-Call-Sid');
|
||||
const application_sid = this.application_sid || uac.res?.get('X-Application-Sid');
|
||||
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
this.activeCallIds.set(this.req.get('Call-ID'), this);
|
||||
const call_sid = uac.res?.get('X-Call-Sid');
|
||||
const application_sid = this.application_sid || uac.res?.get('X-Application-Sid');
|
||||
if (this.req.locals.cdr) {
|
||||
this.req.locals.cdr = {
|
||||
...this.req.locals.cdr,
|
||||
@@ -339,7 +356,8 @@ class CallSession extends Emitter {
|
||||
try {
|
||||
await other.destroy();
|
||||
} catch (err) {}
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
this.unsubscribeForDTMF();
|
||||
|
||||
|
||||
const trackingOn = process.env.JAMBONES_TRACK_ACCOUNT_CALLS ||
|
||||
process.env.JAMBONES_TRACK_SP_CALLS ||
|
||||
@@ -395,8 +413,7 @@ class CallSession extends Emitter {
|
||||
});
|
||||
});
|
||||
|
||||
this.subscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag,
|
||||
this._onDTMF.bind(this, uac));
|
||||
this.subscribeForDTMF(uac);
|
||||
|
||||
uas.on('modify', this._onReinvite.bind(this, uas));
|
||||
uac.on('modify', this._onReinvite.bind(this, uac));
|
||||
@@ -490,7 +507,7 @@ Duration=${payload.duration} `
|
||||
});
|
||||
}
|
||||
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
this.unsubscribeForDTMF();
|
||||
|
||||
const uas = await this.srf.createUAS(req, res, {
|
||||
localSdp: response.sdp,
|
||||
@@ -523,7 +540,7 @@ Duration=${payload.duration} `
|
||||
req.body;
|
||||
|
||||
const reason = req.get('X-Reason');
|
||||
const isReleasingMedia = reason && dlg.type === 'uas' && ['release-media', 'anchor-media'].includes(reason);
|
||||
const isReleasingMedia = reason && dlg.type === 'uac' && ['release-media', 'anchor-media'].includes(reason);
|
||||
const fromTag = dlg.type === 'uas' ? this.rtpEngineOpts.uas.tag : this.rtpEngineOpts.uac.tag;
|
||||
const toTag = dlg.type === 'uas' ? this.rtpEngineOpts.uac.tag : this.rtpEngineOpts.uas.tag;
|
||||
const offerMedia = dlg.type === 'uas' ? this.rtpEngineOpts.uac.mediaOpts : this.rtpEngineOpts.uas.mediaOpts;
|
||||
@@ -756,30 +773,38 @@ Duration=${payload.duration} `
|
||||
const referredBy = req.getParsedHeader('Referred-By');
|
||||
if (!referredBy) return res.send(400);
|
||||
const u = parseUri(referredBy.uri);
|
||||
|
||||
let selectedGateway = false;
|
||||
let e164 = false;
|
||||
if (gateway) {
|
||||
/* host of Refer-to to an outbound gateway */
|
||||
const gw = await this.srf.locals.getOutboundGatewayForRefer(gateway.voip_carrier_sid);
|
||||
if (gw) {
|
||||
selectedGateway = true;
|
||||
e164 = gw.e164_leading_plus;
|
||||
uri.host = gw.ipv4;
|
||||
uri.port = gw.port;
|
||||
const leaveReferToAlone = req.has('X-Refer-To-Leave-Untouched');
|
||||
if (leaveReferToAlone) {
|
||||
this.logger.debug({referTo}, 'passing Refer-To header through untouched');
|
||||
}
|
||||
else {
|
||||
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
||||
let selectedGateway = false;
|
||||
let e164 = false;
|
||||
if (gateway && isDotDecimal) {
|
||||
/* host of Refer-to to an outbound gateway */
|
||||
const gw = await this.srf.locals.getOutboundGatewayForRefer(gateway.voip_carrier_sid);
|
||||
if (gw) {
|
||||
selectedGateway = true;
|
||||
e164 = gw.e164_leading_plus;
|
||||
uri.host = gw.ipv4;
|
||||
uri.port = gw.port;
|
||||
}
|
||||
}
|
||||
if (!selectedGateway && isDotDecimal) {
|
||||
uri.host = this.req.source_address;
|
||||
uri.port = this.req.source_port;
|
||||
}
|
||||
if (e164 && !uri.user.startsWith('+')) {
|
||||
uri.user = `+${uri.user}`;
|
||||
}
|
||||
}
|
||||
if (!selectedGateway) {
|
||||
uri.host = this.req.source_address;
|
||||
uri.port = this.req.source_port;
|
||||
}
|
||||
if (e164 && !uri.user.startsWith('+')) {
|
||||
uri.user = `+${uri.user}`;
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {via, from, to, 'call-id':callid, cseq, 'max-forwards':maxforwards,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
'content-length':contentlength, 'refer-to':_referto, 'referred-by':_referredby,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
'X-Refer-To-Leave-Untouched': _leave,
|
||||
...customHeaders
|
||||
} = req.headers;
|
||||
|
||||
@@ -883,7 +908,7 @@ Duration=${payload.duration} `
|
||||
|
||||
// successfully connected
|
||||
this.logger.info('successfully connected new call leg for REFER');
|
||||
this.unsubscribeDTMF(this.logger, this.req.get('Call-ID'), this.rtpEngineOpts.uas.tag);
|
||||
this.unsubscribeForDTMF();
|
||||
this.referInvite = null;
|
||||
sendNotify(this.uas, '200 OK');
|
||||
this.uas.destroy();
|
||||
|
||||
+134
-97
@@ -24,7 +24,7 @@ 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
|
||||
vc.account_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
|
||||
@@ -93,14 +93,14 @@ module.exports = (srf, logger) => {
|
||||
|
||||
/* if multiple, prefer a DNS name */
|
||||
const hasDns = r.find((row) => row.ipv4.match(/^[A-Za-z]/));
|
||||
return hasDns || r[0];
|
||||
return hasDns /* || r[0] */;
|
||||
} catch (err) {
|
||||
logger.error({err}, 'getOutboundGatewayForRefer');
|
||||
}
|
||||
};
|
||||
|
||||
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
|
||||
const did = normalizeDID(req.calledNumber);
|
||||
const did = normalizeDID(req.calledNumber) || 'anonymous';
|
||||
|
||||
try {
|
||||
/* straight DID match */
|
||||
@@ -125,42 +125,132 @@ module.exports = (srf, logger) => {
|
||||
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;
|
||||
if (!isDotDecimal) {
|
||||
/**
|
||||
* The host part of the SIP URI is not a dot-decimal IP address,
|
||||
* so this can be one of two things:
|
||||
* (1) a sip realm value associate with an account, or
|
||||
* (2) a carrier name for a carrier that we send outbound registrations to
|
||||
*
|
||||
* Let's look for case #1 first...
|
||||
*/
|
||||
|
||||
/* 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;
|
||||
const service_provider_sid = await getSPForAccount(process.env.SBC_ACCOUNT_SID);
|
||||
/* get all the carriers and gateways for the account owning this sip realm */
|
||||
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
||||
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
||||
const gw = gwAcc.concat(gwSP);
|
||||
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: r[0],
|
||||
account_sid: process.env.SBC_ACCOUNT_SID,
|
||||
service_provider_sid
|
||||
gateway: selected,
|
||||
service_provider_sid: a[0].service_provider_sid,
|
||||
account_sid: a[0].account_sid,
|
||||
application_sid: selected.application_sid,
|
||||
account: a[0]
|
||||
};
|
||||
}
|
||||
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..
|
||||
check if we only have a single account, otherwise we have no
|
||||
way of knowing which account this is for
|
||||
/* no match, so let's look for case #2 */
|
||||
try {
|
||||
logger.info({
|
||||
host: uri.host,
|
||||
user: uri.user
|
||||
}, 'sip realm is not associated with an account, checking carriers');
|
||||
const [gw] = await pp.query(sqlSelectCarrierRequiringRegistration, [uri.host, uri.user]);
|
||||
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||
if (1 === matches.length) {
|
||||
// bingo
|
||||
//TODO: this assumes the carrier is associate to an account, not an SP
|
||||
//if the carrier is associated with an SP (which would mean we
|
||||
//must see a dialed number in the To header, not the register username),
|
||||
//then we need to look up the account based on the dialed number in the To header
|
||||
const [a] = await pp.query(sqlAccountBySid, matches[0].account_sid);
|
||||
if (0 === a.length) return failure;
|
||||
logger.debug({matches}, `found registration carrier using ${uri.host} and ${uri.user}`);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: matches[0],
|
||||
service_provider_sid: a[0].service_provider_sid,
|
||||
account_sid: a[0].account_sid,
|
||||
application_sid: matches[0].application_sid,
|
||||
account: a[0]
|
||||
};
|
||||
}
|
||||
else if (matches.length > 1) {
|
||||
logger.warn({matches, source_address: req.source_address}, 'multiple gateways match source address');
|
||||
return {
|
||||
fromCarrier: true,
|
||||
error: 'Multiple gateways match registration carrier source address'
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
logger.info({err, host: uri.host, user: uri.user}, 'Error looking up carrier by host and user');
|
||||
}
|
||||
/* no match, so fall through */
|
||||
}
|
||||
|
||||
if (isDotDecimal && 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;
|
||||
const service_provider_sid = await getSPForAccount(process.env.SBC_ACCOUNT_SID);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: r[0],
|
||||
account_sid: process.env.SBC_ACCOUNT_SID,
|
||||
service_provider_sid
|
||||
};
|
||||
}
|
||||
else {
|
||||
/* find all carrier entries that have an inbound gateway matching the source IP */
|
||||
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
||||
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||
if (matches.length) {
|
||||
/* we have one or more matches. Now check for one with a provisioned phone number matching the DID */
|
||||
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
|
||||
const did = normalizeDID(req.calledNumber) || 'anonymous';
|
||||
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 provisioned carrier, but the dialed number is not provisioned.
|
||||
check if we have an account with default routing of that carrier to an application
|
||||
*/
|
||||
const accountLevelGateways = matches.filter((m) => m.account_sid && m.application_sid);
|
||||
if (accountLevelGateways.length > 1) {
|
||||
logger.info({accounts: accountLevelGateways.map((m) => m.account_sid)},
|
||||
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||
return {
|
||||
fromCarrier: true,
|
||||
error: 'Multiple accounts are attempting to default route this carrier'
|
||||
};
|
||||
}
|
||||
else if (accountLevelGateways.length === 1) {
|
||||
const [accounts] = await pp.query('SELECT * from accounts where account_sid = ?',
|
||||
accountLevelGateways[0].account_sid);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: accountLevelGateways[0],
|
||||
service_provider_sid: accountLevelGateways[0].service_provider_sid,
|
||||
account_sid: accountLevelGateways[0].account_sid,
|
||||
application_sid: accountLevelGateways[0].application_sid,
|
||||
account: accounts[0]
|
||||
};
|
||||
}
|
||||
else {
|
||||
/* check if we only have a single account, otherwise we have no
|
||||
- way of knowing which account this is for
|
||||
*/
|
||||
const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?',
|
||||
matches[0].service_provider_sid);
|
||||
if (r[0].count === 0) return {fromCarrier: true};
|
||||
if (r[0].count === 0 || r[0].count > 1) return {fromCarrier: true};
|
||||
else {
|
||||
const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?',
|
||||
matches[0].service_provider_sid);
|
||||
@@ -173,83 +263,30 @@ module.exports = (srf, logger) => {
|
||||
};
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if (r.length > 1) {
|
||||
logger.info({r},
|
||||
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway,
|
||||
service_provider_sid: accounts[0].service_provider_sid,
|
||||
account_sid: r[0].account_sid,
|
||||
application_sid: r[0].application_sid,
|
||||
account: accounts[0]
|
||||
error: 'Multiple accounts are attempting to route the same phone number from the same carrier'
|
||||
};
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The host part of the SIP URI is not a dot-decimal IP address,
|
||||
* so this can be one of two things:
|
||||
* (1) a sip realm value associate with an account, or
|
||||
* (2) a carrier name for a carrier that we send outbound registrations to
|
||||
*
|
||||
* Let's look for case #1 first...
|
||||
*/
|
||||
|
||||
/* get all the carriers and gateways for the account owning this sip realm */
|
||||
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
||||
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
||||
const gw = gwAcc.concat(gwSP);
|
||||
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,
|
||||
service_provider_sid: a[0].service_provider_sid,
|
||||
account_sid: a[0].account_sid,
|
||||
application_sid: selected.application_sid,
|
||||
account: a[0]
|
||||
};
|
||||
}
|
||||
|
||||
/* no match, so let's look for case #2 */
|
||||
try {
|
||||
logger.info({
|
||||
host: uri.host,
|
||||
user: uri.user
|
||||
}, 'sip realm is not associated with an account, checking carriers');
|
||||
const [gw] = await pp.query(sqlSelectCarrierRequiringRegistration, [uri.host, uri.user]);
|
||||
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
||||
if (1 === matches.length) {
|
||||
// bingo
|
||||
//TODO: this assumes the carrier is associate to an account, not an SP
|
||||
//if the carrier is associated with an SP (which would mean we
|
||||
//must see a dialed number in the To header, not the register username),
|
||||
//then we need to look up the account based on the dialed number in the To header
|
||||
const [a] = await pp.query(sqlAccountBySid, matches[0].account_sid);
|
||||
if (0 === a.length) return failure;
|
||||
logger.debug({matches}, `found registration carrier using ${uri.host} and ${uri.user}`);
|
||||
/* we have a route for this phone number and carrier combination */
|
||||
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: matches[0],
|
||||
service_provider_sid: a[0].service_provider_sid,
|
||||
account_sid: a[0].account_sid,
|
||||
application_sid: matches[0].application_sid,
|
||||
account: a[0]
|
||||
gateway,
|
||||
service_provider_sid: accounts[0].service_provider_sid,
|
||||
account_sid: r[0].account_sid,
|
||||
application_sid: r[0].application_sid,
|
||||
account: accounts[0]
|
||||
};
|
||||
|
||||
}
|
||||
else if (matches.length > 1) {
|
||||
logger.warn({matches, source_address: req.source_address}, 'multiple gateways match source address');
|
||||
}
|
||||
} catch (err) {
|
||||
logger.info({err, host: uri.host, user: uri.user}, 'Error looking up carrier by host and user');
|
||||
}
|
||||
|
||||
return failure;
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -15,7 +15,9 @@ module.exports = (srf, logger) => {
|
||||
return async() => {
|
||||
try {
|
||||
if (process.env.K8S) {
|
||||
return process.env.K8S_FEATURE_SERVER_SERVICE_NAME;
|
||||
return process.env.K8S_FEATURE_SERVER_TRANSPORT ?
|
||||
`${process.env.K8S_FEATURE_SERVER_SERVICE_NAME};transport=${process.env.K8S_FEATURE_SERVER_TRANSPORT}` :
|
||||
process.env.K8S_FEATURE_SERVER_SERVICE_NAME;
|
||||
}
|
||||
else {
|
||||
const fs = await retrieveSet(setName);
|
||||
|
||||
+15
-3
@@ -2,7 +2,7 @@ const debug = require('debug')('jambonz:sbc-inbound');
|
||||
const assert = require('assert');
|
||||
const Emitter = require('events');
|
||||
const parseUri = require('drachtio-srf').parseUri;
|
||||
const {nudgeCallCounts} = require('./utils');
|
||||
const {nudgeCallCounts, roundTripTime} = require('./utils');
|
||||
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
|
||||
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
|
||||
[];
|
||||
@@ -135,15 +135,20 @@ module.exports = function(srf, logger) {
|
||||
const identifyAccount = async(req, res, next) => {
|
||||
try {
|
||||
const {siprec, callId} = req.locals;
|
||||
const {getSPForAccount, wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = req.srf.locals;
|
||||
const {getSPForAccount, wasOriginatedFromCarrier, getApplicationForDidAndCarrier, stats} = req.srf.locals;
|
||||
const startAt = process.hrtime();
|
||||
const {
|
||||
fromCarrier,
|
||||
gateway,
|
||||
account_sid,
|
||||
application_sid,
|
||||
service_provider_sid,
|
||||
account
|
||||
account,
|
||||
error
|
||||
} = await wasOriginatedFromCarrier(req);
|
||||
const rtt = roundTripTime(startAt);
|
||||
stats.histogram('app.mysql.response_time', rtt, [
|
||||
'query:wasOriginatedFromCarrier', 'app:sbc-inbound']);
|
||||
/**
|
||||
* calls come from 3 sources:
|
||||
* (1) A carrier
|
||||
@@ -151,6 +156,13 @@ module.exports = function(srf, logger) {
|
||||
* (3) A SIP user
|
||||
*/
|
||||
if (fromCarrier) {
|
||||
if (error) {
|
||||
return res.send(503, {
|
||||
headers: {
|
||||
'X-Reason': error
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!gateway) {
|
||||
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
|
||||
return res.send(404, 'Number Not Provisioned');
|
||||
|
||||
+6
-1
@@ -26,7 +26,8 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
}
|
||||
const common = {
|
||||
'call-id': req.get('Call-ID'),
|
||||
'replace': ['origin', 'session-connection']
|
||||
'replace': ['origin', 'session-connection'],
|
||||
'record call': process.env.JAMBONES_RECORD_ALL_CALLS ? 'yes' : 'no'
|
||||
};
|
||||
return {
|
||||
common,
|
||||
@@ -41,6 +42,9 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
|
||||
};
|
||||
}
|
||||
|
||||
const SdpWantsSDES = (sdp) => {
|
||||
return /m=audio.*\s+RTP\/SAVP/.test(sdp);
|
||||
};
|
||||
const SdpWantsSrtp = (sdp) => {
|
||||
return /m=audio.*SAVP/.test(sdp);
|
||||
};
|
||||
@@ -178,6 +182,7 @@ const parseConnectionIp = (sdp) => {
|
||||
module.exports = {
|
||||
isWSS,
|
||||
SdpWantsSrtp,
|
||||
SdpWantsSDES,
|
||||
getAppserver,
|
||||
makeRtpEngineOpts,
|
||||
makeAccountCallCountKey,
|
||||
|
||||
Generated
+4370
-1760
File diff suppressed because it is too large
Load Diff
+7
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "v0.7.7",
|
||||
"version": "v0.7.8",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -25,24 +25,22 @@
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.7.0",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.35",
|
||||
"@jambonz/db-helpers": "^0.7.4",
|
||||
"@jambonz/http-authenticator": "^0.2.2",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/rtpengine-utils": "^0.3.10",
|
||||
"@jambonz/siprec-client-utils": "^0.1.4",
|
||||
"@jambonz/realtimedb-helpers": "^0.6.3",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/siprec-client-utils": "^0.2.0",
|
||||
"@jambonz/stats-collector": "^0.1.6",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"aws-sdk": "^2.1152.0",
|
||||
"aws-sdk": "^2.1261.0",
|
||||
"bent": "^7.3.12",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"drachtio-fn-b2b-sugar": "0.0.12",
|
||||
"drachtio-srf": "^4.5.1",
|
||||
"drachtio-srf": "^4.5.21",
|
||||
"express": "^4.18.1",
|
||||
"pino": "^7.11.0",
|
||||
"sdp-transform": "^2.14.1",
|
||||
"uuid": "^8.3.2",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
|
||||
+40
-27
@@ -4,12 +4,12 @@ SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS account_static_ips;
|
||||
|
||||
DROP TABLE IF EXISTS account_limits;
|
||||
|
||||
DROP TABLE IF EXISTS account_products;
|
||||
|
||||
DROP TABLE IF EXISTS account_subscriptions;
|
||||
|
||||
DROP TABLE IF EXISTS account_limits;
|
||||
|
||||
DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
@@ -20,6 +20,8 @@ DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS password_settings;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
||||
@@ -38,10 +40,10 @@ DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS service_provider_limits;
|
||||
|
||||
DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS smpp_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS speech_credentials;
|
||||
@@ -73,6 +75,15 @@ private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (account_static_ip_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_limits
|
||||
(
|
||||
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_subscriptions
|
||||
(
|
||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -92,15 +103,6 @@ pending_reason VARBINARY(52),
|
||||
PRIMARY KEY (account_subscription_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_limits
|
||||
(
|
||||
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE beta_invite_codes
|
||||
(
|
||||
invite_code CHAR(6) NOT NULL UNIQUE ,
|
||||
@@ -136,6 +138,13 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE password_settings
|
||||
(
|
||||
min_password_length INTEGER NOT NULL DEFAULT 8,
|
||||
require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TABLE predefined_carriers
|
||||
(
|
||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -241,6 +250,15 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
CREATE TABLE service_provider_limits
|
||||
(
|
||||
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (service_provider_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE signup_history
|
||||
(
|
||||
email VARCHAR(255) NOT NULL,
|
||||
@@ -249,15 +267,6 @@ signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (email)
|
||||
);
|
||||
|
||||
CREATE TABLE service_provider_limits
|
||||
(
|
||||
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (service_provider_limits_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE smpp_addresses
|
||||
(
|
||||
smpp_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -305,6 +314,7 @@ 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,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
@@ -332,6 +342,9 @@ smpp_password VARCHAR(64),
|
||||
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
||||
smpp_inbound_system_id VARCHAR(255),
|
||||
smpp_inbound_password VARCHAR(64),
|
||||
register_from_user VARCHAR(128),
|
||||
register_from_domain VARCHAR(255),
|
||||
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -452,12 +465,12 @@ CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_
|
||||
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_sid_idx ON account_limits (account_sid);
|
||||
ALTER TABLE account_limits ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
|
||||
|
||||
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 account_sid_idx ON account_limits (account_sid);
|
||||
ALTER TABLE account_limits ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
|
||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_2 (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);
|
||||
@@ -512,10 +525,10 @@ ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) R
|
||||
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 service_provider_sid_idx ON service_provider_limits (service_provider_sid);
|
||||
ALTER TABLE service_provider_limits ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid) ON DELETE CASCADE;
|
||||
|
||||
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_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
@@ -9,7 +9,8 @@ insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2
|
||||
|
||||
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||
insert into service_provider_limits (service_provider_limits_sid, service_provider_sid, category, quantity) VALUES ('a79d3ade-e0da-4461-80f3-7c73f01e18b4', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'voice_call_session', 1);
|
||||
insert into service_provider_limits (service_provider_limits_sid, service_provider_sid, category, quantity)
|
||||
values ('a79d3ade-e0da-4461-80f3-7c73f01e18b4', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'voice_call_session', 1);
|
||||
|
||||
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');
|
||||
@@ -66,4 +67,25 @@ insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_s
|
||||
values ('d458bf7a-bcea-47b2-ac96-66dfc9c5c220', '150822233*', '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');
|
||||
values ('f7ad205d-b92f-4363-8160-f8b5216b40d3', '15083871234', '287c1452-620d-4195-9f19-c9814ef90d78', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
|
||||
|
||||
-- two accounts that both have the same carrier with default routing (ambiguity test)
|
||||
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||
values ('239d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account B1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'echo2.sip.jambonz.org');
|
||||
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
|
||||
values ('909d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account B2', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'foxtrot.sip.jambonz.org');
|
||||
|
||||
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
|
||||
values ('8843e39f-4346-4218-8434-a53130e8be49', 'test', '239d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
|
||||
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
|
||||
values ('7743e39f-4346-4218-8434-a53130e8be49', 'test', '909d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
|
||||
|
||||
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, account_sid, application_sid)
|
||||
values ('731abdc7-0220-4964-bc66-32b5c70cd9ab', 'twilio-1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '239d7d49-b3e4-4fdb-9d66-661149f717e8', '8843e39f-4346-4218-8434-a53130e8be49');
|
||||
insert into voip_carriers (voip_carrier_sid, name, service_provider_sid, account_sid, application_sid)
|
||||
values ('987abdc7-0220-4964-bc66-32b5c70cd9ab', 'twilio-2', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', '909d7d49-b3e4-4fdb-9d66-661149f717e8', '7743e39f-4346-4218-8434-a53130e8be49');
|
||||
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('664a5339-c62c-4075-9e19-f4de70a96597', '731abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.40', true, false);
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('554a5339-c62c-4075-9e19-f4de70a96597', '987abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.40', true, false);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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@172.38.0.10 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@172.38.0.10>
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 INVITE
|
||||
Contact: sip:sipp@[local_ip]:[local_port]
|
||||
Max-Forwards: 70
|
||||
Subject: uac-pcap-carrier-fail-ambiguous
|
||||
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@172.38.0.10 SIP/2.0
|
||||
[last_Via]
|
||||
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
|
||||
To: <sip:15083871234@172.38.0.10>[peer_tag_param]
|
||||
Call-ID: [call_id]
|
||||
CSeq: 1 ACK
|
||||
Max-Forwards: 70
|
||||
Subject: uac-pcap-carrier-fail-ambiguous
|
||||
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>
|
||||
+7
-3
@@ -33,7 +33,6 @@ test('incoming call tests', async(t) => {
|
||||
t.ok(obj.calls === 0, 'HTTP GET / works (current call count)')
|
||||
obj = await getJSON('http://127.0.0.1:3050/system-health');
|
||||
t.ok(obj.calls === 0, 'HTTP GET /system-health works (health check)')
|
||||
|
||||
await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
|
||||
t.pass('incoming call from carrier completed successfully');
|
||||
|
||||
@@ -59,11 +58,16 @@ test('incoming call tests', async(t) => {
|
||||
t.pass('handles in-dialog requests');
|
||||
|
||||
await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
|
||||
t.pass('rejects incoming call with 503 when max calls per account reached')
|
||||
t.pass('rejects incoming call with 503 when max calls per account reached');
|
||||
|
||||
/* switch off this env for remaining tests (JAMBONES_HOSTING is for Saas sts) */
|
||||
delete process.env.JAMBONES_HOSTING;
|
||||
await sippUac('uac-pcap-carrier-fail-ambiguous.xml', '172.38.0.40');
|
||||
t.pass('rejects incoming call with 503 when multiple accounts have same carrier witrh default routing')
|
||||
|
||||
await waitFor(12);
|
||||
const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
|
||||
console.log(`cdrs: ${JSON.stringify(res)}`);
|
||||
//console.log(`cdrs: ${JSON.stringify(res)}`);
|
||||
t.ok(7 === res.total, 'successfully wrote 7 cdrs for calls');
|
||||
|
||||
srf.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user