mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d8ba93b2 | ||
|
|
3f729e232e | ||
|
|
509fdf6c37 | ||
|
|
b7b707cc2e | ||
|
|
ccbebc015f | ||
|
|
320a30e328 | ||
|
|
cd2fde360d | ||
|
|
e353b751c0 | ||
|
|
53b46f7e51 | ||
|
|
6608286e8c | ||
|
|
6ddfbc9373 | ||
|
|
cd48675499 | ||
|
|
cecdbdccef | ||
|
|
6586919c86 | ||
|
|
c919438af5 | ||
|
|
ab39525467 | ||
|
|
61a66ab181 | ||
|
|
48dd7ebfcd | ||
|
|
678fe9d9a8 | ||
|
|
39bd65fb97 | ||
|
|
3f0fd794ce | ||
|
|
876cb393f9 | ||
|
|
fa2259d6e5 | ||
|
|
2405190dcc | ||
|
|
e9a5921b19 | ||
|
|
6efe714d49 | ||
|
|
4150454736 | ||
|
|
73eb8e8d17 |
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: prepare tag
|
||||
id: prepare_tag
|
||||
@@ -37,14 +37,14 @@ jobs:
|
||||
echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
FROM --platform=linux/amd64 node:20.13.0-alpine3.18 as base
|
||||
FROM --platform=linux/amd64 node:24-alpine AS base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
WORKDIR /opt/app/
|
||||
|
||||
FROM base as build
|
||||
FROM base AS build
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
@@ -18,6 +18,6 @@ COPY --from=build /opt/app /opt/app/
|
||||
|
||||
ARG NODE_ENV
|
||||
|
||||
ENV NODE_ENV $NODE_ENV
|
||||
ENV NODE_ENV=$NODE_ENV
|
||||
|
||||
CMD [ "node", "app.js" ]
|
||||
|
||||
@@ -64,12 +64,21 @@ const {
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||
}, logger);
|
||||
}, logger, process.env.JAMBONES_MYSQL_WRITE_HOST && process.env.JAMBONES_MYSQL_WRITE_USER &&
|
||||
process.env.JAMBONES_MYSQL_WRITE_PASSWORD && process.env.JAMBONES_MYSQL_WRITE_DATABASE ? {
|
||||
host: process.env.JAMBONES_MYSQL_WRITE_HOST,
|
||||
port: process.env.JAMBONES_MYSQL_WRITE_PORT || 3306,
|
||||
user: process.env.JAMBONES_MYSQL_WRITE_USER,
|
||||
password: process.env.JAMBONES_MYSQL_WRITE_PASSWORD,
|
||||
database: process.env.JAMBONES_MYSQL_WRITE_DATABASE,
|
||||
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||
} : null);
|
||||
const {
|
||||
client: redisClient,
|
||||
addKey,
|
||||
deleteKey,
|
||||
retrieveKey,
|
||||
retrieveHash,
|
||||
createSet,
|
||||
retrieveSet,
|
||||
addToSet,
|
||||
@@ -117,6 +126,7 @@ srf.locals = {...srf.locals,
|
||||
addKey,
|
||||
deleteKey,
|
||||
retrieveKey,
|
||||
retrieveHash,
|
||||
createSet,
|
||||
incrKey,
|
||||
decrKey,
|
||||
@@ -145,6 +155,17 @@ srf.locals = {
|
||||
};
|
||||
const activeCallIds = srf.locals.activeCallIds;
|
||||
|
||||
/* report our call count to redis so a draining process can count calls across
|
||||
all sbc-inbound and sbc-outbound processes on this server */
|
||||
if (!process.env.K8S && 'test' !== process.env.NODE_ENV) {
|
||||
srf.locals.callCountReporter = require('./lib/call-count-reporter')({
|
||||
logger,
|
||||
addKey,
|
||||
addToSet,
|
||||
getCount: () => activeCallIds.size
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
initLocals,
|
||||
handleSipRec,
|
||||
@@ -260,7 +281,7 @@ srf.invite((req, res) => {
|
||||
session.connect();
|
||||
});
|
||||
|
||||
srf.use((req, res, next, err) => {
|
||||
srf.use((err, req, res, next) => {
|
||||
logger.error(err, 'hit top-level error handler');
|
||||
res.send(500);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ICE": "default",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "port latching"],
|
||||
"rtcp-mux": ["require"]
|
||||
"rtcp-mux": ["offer"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ module.exports = [
|
||||
// Variables
|
||||
'no-delete-var': 2,
|
||||
'no-undef': 2,
|
||||
'no-unused-vars': [2, {args: 'none'}],
|
||||
'no-unused-vars': [2, {args: 'none', ignoreRestSiblings: true}],
|
||||
|
||||
// Node.js and CommonJS
|
||||
'no-mixed-requires': 2,
|
||||
|
||||
@@ -23,23 +23,55 @@ module.exports = (logger) => {
|
||||
const {srf} = require('..');
|
||||
const {activeCallIds, removeFromRedis} = srf.locals;
|
||||
|
||||
/* reject new INVITEs with 503 so senders fail over to another SBC */
|
||||
srf.locals.dryUpCalls = true;
|
||||
|
||||
/* remove our private IP from the set of active SBCs so rtp and fs know we are gone */
|
||||
removeFromRedis();
|
||||
|
||||
/* if we have zero calls, we can complete the scale-in right now */
|
||||
const calls = activeCallIds.size;
|
||||
if (0 === calls) {
|
||||
logger.info('scale-in can complete immediately as we have no calls in progress');
|
||||
lifecycleEmitter.completeScaleIn();
|
||||
}
|
||||
else {
|
||||
logger.info(`${calls} calls in progress; scale-in will complete when they are done`);
|
||||
}
|
||||
/* count calls in progress across all sbc-inbound and sbc-outbound
|
||||
processes on this server, if they are reporting; otherwise
|
||||
fall back to counting only our own */
|
||||
const countServerCalls = async() => {
|
||||
const reporter = srf.locals.callCountReporter;
|
||||
if (!reporter) return activeCallIds.size;
|
||||
const {retrieveSet, retrieveKey} = srf.locals.realtimeDbHelpers;
|
||||
const keys = await retrieveSet(reporter.setName);
|
||||
let count = 0;
|
||||
for (const key of keys) {
|
||||
count += parseInt(await retrieveKey(key), 10) || 0;
|
||||
}
|
||||
return Math.max(count, activeCallIds.size);
|
||||
};
|
||||
|
||||
/* poll until calls have dried up, then complete the scale-in;
|
||||
require two consecutive zero readings since reported counts
|
||||
may be up to 15s stale */
|
||||
let consecutiveZeroCounts = 0;
|
||||
const timer = setInterval(async() => {
|
||||
try {
|
||||
const calls = await countServerCalls();
|
||||
if (0 === calls) {
|
||||
if (++consecutiveZeroCounts >= 2) {
|
||||
clearInterval(timer);
|
||||
logger.info('scale-in complete now that calls have dried up');
|
||||
lifecycleEmitter.completeScaleIn();
|
||||
}
|
||||
}
|
||||
else {
|
||||
consecutiveZeroCounts = 0;
|
||||
logger.info(`${calls} calls in progress on this server; scale-in will complete when they are done`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, 'Error counting calls in progress during scale-in');
|
||||
}
|
||||
}, 20000);
|
||||
})
|
||||
.on(LifeCycleEvents.StandbyEnter, () => {
|
||||
lifecycleEmitter.dryUpCalls = true;
|
||||
const {srf} = require('..');
|
||||
const {removeFromRedis} = srf.locals;
|
||||
srf.locals.dryUpCalls = true;
|
||||
removeFromRedis();
|
||||
|
||||
logger.info('AWS enter pending state notification: begin drying up calls');
|
||||
@@ -48,6 +80,7 @@ module.exports = (logger) => {
|
||||
lifecycleEmitter.dryUpCalls = false;
|
||||
const {srf} = require('..');
|
||||
const {addToRedis} = srf.locals;
|
||||
srf.locals.dryUpCalls = false;
|
||||
addToRedis();
|
||||
|
||||
logger.info('AWS exit pending state notification: re-enable calls');
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
const os = require('os');
|
||||
|
||||
/**
|
||||
* Periodically report this process's count of calls in progress to redis.
|
||||
* A server may host several sbc-inbound and sbc-outbound processes; when one
|
||||
* of them handles an autoscale drain it needs to know when the entire server
|
||||
* has no calls in progress, not just its own process. Each process writes
|
||||
* its own count under a per-pid key (with a short expiry, so keys from dead
|
||||
* processes evaporate) and registers that key in a per-host set that the
|
||||
* draining process can enumerate.
|
||||
*/
|
||||
const REPORT_INTERVAL = 15000;
|
||||
const KEY_EXPIRY_SECS = 120;
|
||||
|
||||
module.exports = ({logger, addKey, addToSet, getCount}) => {
|
||||
const prefix = process.env.JAMBONES_CLUSTER_ID || 'default';
|
||||
const setName = `${prefix}:call-count-keys:${os.hostname()}`;
|
||||
const key = `${prefix}:call-count:${os.hostname()}:${process.pid}`;
|
||||
|
||||
const report = () => {
|
||||
addKey(key, `${getCount()}`, KEY_EXPIRY_SECS)
|
||||
.catch((err) => logger.error({err}, 'call-count-reporter: error writing call count'));
|
||||
};
|
||||
|
||||
addToSet(setName, key)
|
||||
.catch((err) => logger.error({err}, `call-count-reporter: error adding ${key} to ${setName}`));
|
||||
setInterval(report, REPORT_INTERVAL);
|
||||
report();
|
||||
|
||||
return {key, setName};
|
||||
};
|
||||
+19
-2
@@ -66,6 +66,7 @@ class CallSession extends Emitter {
|
||||
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
|
||||
this.addKey = req.srf.locals.realtimeDbHelpers.addKey;
|
||||
this.retrieveKey = req.srf.locals.realtimeDbHelpers.retrieveKey;
|
||||
this.retrieveHash = req.srf.locals.realtimeDbHelpers.retrieveHash;
|
||||
|
||||
this._mediaPath = MediaPath.FullMedia;
|
||||
|
||||
@@ -108,7 +109,11 @@ class CallSession extends Emitter {
|
||||
|
||||
async connect() {
|
||||
const {sdp} = this.req.locals;
|
||||
const is3pcc = this.req.body?.length === 0;
|
||||
// use the parsed SDP from middleware (req.locals.sdp), not req.body:
|
||||
// for multipart (SIPREC) or IWF-originated bodies req.body may be empty
|
||||
// or not represent the actual offer, which would wrongly send an
|
||||
// SDP-bearing call down the no-offer 3pcc path.
|
||||
const is3pcc = !sdp || sdp.length === 0;
|
||||
this.logger.info(`inbound ${is3pcc ? '3pcc ' : ''}call accepted for routing`);
|
||||
const engine = this.getRtpEngine();
|
||||
if (!engine) {
|
||||
@@ -332,6 +337,15 @@ class CallSession extends Emitter {
|
||||
},
|
||||
});
|
||||
|
||||
// passing gateway outbound auth that sbc-inbound can send RE-INVITE with authentication process on UAS side
|
||||
const {gateway} = this.req.locals;
|
||||
if (gateway && gateway.register_username && gateway.register_password) {
|
||||
this.logger.debug('passing outbound gateway auth to CallSession for reinvite processing');
|
||||
uas.auth = {
|
||||
username: gateway.register_username,
|
||||
password: gateway.register_password
|
||||
};
|
||||
}
|
||||
// successfully connected
|
||||
this.logger.info('call connected successfully to feature server');
|
||||
debug('call connected successfully to feature server');
|
||||
@@ -482,7 +496,9 @@ class CallSession extends Emitter {
|
||||
this.req.locals.carrier :
|
||||
this.req.locals.originator;
|
||||
const application = await this.srf.locals.getApplicationBySid(application_sid);
|
||||
const isRecording = this.req.locals.account.record_all_calls || (application && application.record_all_calls);
|
||||
const {hasRecording} = await this.retrieveHash(`call:${this.account_sid}:${call_sid}`) ?? {};
|
||||
const isRecording = this.req.locals.account.record_all_calls ||
|
||||
(application && application.record_all_calls) || hasRecording;
|
||||
const day = new Date();
|
||||
let recording_url = `/Accounts/${this.account_sid}/RecentCalls/${call_sid}/record`;
|
||||
recording_url += `/${day.getFullYear()}/${(day.getMonth() + 1).toString().padStart(2, '0')}`;
|
||||
@@ -1093,6 +1109,7 @@ Duration=${payload.duration} `
|
||||
this.rtpEngineResource.destroy();
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
uac.other.destroy();
|
||||
this._stopRecording();
|
||||
this.srf.endSession(this.req);
|
||||
});
|
||||
|
||||
|
||||
+68
-22
@@ -7,7 +7,8 @@ const sqlSelectSPForAccount = 'SELECT service_provider_sid FROM accounts WHERE a
|
||||
|
||||
const sqlSelectAllCarriersForAccountByRealm =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
||||
WHERE acc.sip_realm = ?
|
||||
AND vc.account_sid = acc.account_sid
|
||||
@@ -17,7 +18,8 @@ AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
||||
|
||||
const sqlSelectAllCarriersForSPByRealm =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
||||
WHERE acc.sip_realm = ?
|
||||
AND vc.service_provider_sid = acc.service_provider_sid
|
||||
@@ -26,14 +28,30 @@ AND vc.is_active = 1
|
||||
AND sg.inbound = 1
|
||||
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.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
|
||||
const sqlSelectExactGatewayForSP =
|
||||
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
||||
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc
|
||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
AND vc.service_provider_sid IS NOT NULL
|
||||
AND vc.is_active = 1
|
||||
AND sg.inbound = 1`;
|
||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
AND vc.service_provider_sid IS NOT NULL
|
||||
AND vc.is_active = 1
|
||||
AND sg.inbound = 1
|
||||
AND sg.netmask = 32
|
||||
AND sg.ipv4 = ?
|
||||
ORDER BY vc.account_sid IS NOT NULL DESC`;
|
||||
|
||||
const sqlSelectCIDRGatewaysForSP =
|
||||
`SELECT STRAIGHT_JOIN sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
||||
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc
|
||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
AND vc.service_provider_sid IS NOT NULL
|
||||
AND vc.is_active = 1
|
||||
AND sg.inbound = 1
|
||||
AND sg.netmask < 32
|
||||
ORDER BY sg.netmask DESC`;
|
||||
|
||||
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ? AND is_active = 1';
|
||||
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
|
||||
@@ -57,7 +75,8 @@ AND outbound = 1`;
|
||||
|
||||
const sqlSelectCarrierRequiringRegistration = `
|
||||
SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid, vc.account_sid,
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
|
||||
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc
|
||||
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
AND vc.requires_register = 1
|
||||
@@ -77,7 +96,8 @@ AND (
|
||||
|
||||
const sqlSelectGatewaysByVoipCarrierSids = `
|
||||
SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
||||
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto
|
||||
vc.account_sid, vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask, sg.pad_crypto,
|
||||
vc.register_username, vc.register_password
|
||||
FROM sip_gateways sg, voip_carriers vc
|
||||
WHERE sg.voip_carrier_sid IN (?)
|
||||
AND sg.voip_carrier_sid = vc.voip_carrier_sid
|
||||
@@ -294,7 +314,7 @@ module.exports = (srf, logger) => {
|
||||
|
||||
/* 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 [gwSP] = await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
||||
const gw = gwAcc
|
||||
.concat(gwSP)
|
||||
.sort((a, b) => b.netmask - a.netmask);
|
||||
@@ -305,7 +325,9 @@ module.exports = (srf, logger) => {
|
||||
name: gw.name,
|
||||
service_provider_sid: gw.service_provider_sid,
|
||||
account_sid: gw.account_sid,
|
||||
application_sid: gw.application_sid
|
||||
application_sid: gw.application_sid,
|
||||
register_username: gw.register_username,
|
||||
register_password: gw.register_password
|
||||
};
|
||||
});
|
||||
/* remove duplicates, winnow down to voip_carriers, not gateways */
|
||||
@@ -326,7 +348,8 @@ module.exports = (srf, logger) => {
|
||||
service_provider_sid: gw.service_provider_sid,
|
||||
account_sid: gw.account_sid,
|
||||
application_sid: gw.application_sid,
|
||||
pad_crypto: gw.pad_crypto
|
||||
register_username: gw.register_username,
|
||||
register_password: gw.register_password
|
||||
};
|
||||
});
|
||||
/* remove duplicates */
|
||||
@@ -371,8 +394,13 @@ module.exports = (srf, logger) => {
|
||||
}
|
||||
}
|
||||
if (r.length > 1) {
|
||||
logger.info({r},
|
||||
'multiple carriers with the same gateway have the same number provisioned for the same account'
|
||||
const all = r.map(({account_sid, voip_carrier_sid}) => ({account_sid, voip_carrier_sid}));
|
||||
logger.info({
|
||||
number: r[0].number,
|
||||
total: all.length,
|
||||
matches: all.slice(0, 5)
|
||||
},
|
||||
'multiple carriers with the same gateway have the same number provisioned for the same account'
|
||||
+ ' -- cannot determine which one to use');
|
||||
return {
|
||||
fromCarrier: true,
|
||||
@@ -447,10 +475,19 @@ module.exports = (srf, logger) => {
|
||||
}
|
||||
|
||||
/* find all carrier entries that have an inbound gateway matching the source IP */
|
||||
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
||||
/* Query both exact IP matches AND CIDR ranges in parallel to handle the case where
|
||||
multiple accounts have configured the same carrier with different netmasks.
|
||||
The phone number lookup will disambiguate which account owns the call. */
|
||||
const [[gwExact], [gwCidr]] = await Promise.all([
|
||||
pp.query(sqlSelectExactGatewayForSP, [req.source_address]),
|
||||
pp.query(sqlSelectCIDRGatewaysForSP)
|
||||
]);
|
||||
|
||||
/* Merge both result sets - exact matches first, then CIDR ranges (already sorted by netmask DESC) */
|
||||
const gw = [...gwExact, ...gwCidr];
|
||||
|
||||
//logger.debug({gw}, `checking gateways for source address ${req.source_address}`);
|
||||
let matches = gw
|
||||
.sort((a, b) => b.netmask - a.netmask)
|
||||
.filter(gatewayMatchesSourceAddress.bind(null, logger, req.source_address))
|
||||
.map((gw) => {
|
||||
return {
|
||||
@@ -459,7 +496,9 @@ module.exports = (srf, logger) => {
|
||||
service_provider_sid: gw.service_provider_sid,
|
||||
account_sid: gw.account_sid,
|
||||
application_sid: gw.application_sid,
|
||||
pad_crypto: gw.pad_crypto
|
||||
pad_crypto: gw.pad_crypto,
|
||||
register_username: gw.register_username,
|
||||
register_password: gw.register_password
|
||||
};
|
||||
});
|
||||
/* remove duplicates, winnow down to voip_carriers, not gateways */
|
||||
@@ -479,7 +518,9 @@ module.exports = (srf, logger) => {
|
||||
service_provider_sid: gw.service_provider_sid,
|
||||
account_sid: gw.account_sid,
|
||||
application_sid: gw.application_sid,
|
||||
pad_crypto: gw.pad_crypto
|
||||
pad_crypto: gw.pad_crypto,
|
||||
register_username: gw.register_username,
|
||||
register_password: gw.register_password
|
||||
};
|
||||
});
|
||||
/* remove duplicates */
|
||||
@@ -565,8 +606,13 @@ module.exports = (srf, logger) => {
|
||||
}
|
||||
}
|
||||
else if (r.length > 1) {
|
||||
logger.info({r},
|
||||
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||
const all = r.map(({account_sid, voip_carrier_sid}) => ({account_sid, voip_carrier_sid}));
|
||||
logger.info({
|
||||
number: r[0].number,
|
||||
total: all.length,
|
||||
matches: all.slice(0, 5)
|
||||
},
|
||||
'multiple accounts have added this carrier with default routing -- cannot determine which to use');
|
||||
return {
|
||||
fromCarrier: true,
|
||||
error: 'Multiple accounts are attempting to route the same phone number from the same carrier'
|
||||
|
||||
+11
-1
@@ -34,6 +34,15 @@ module.exports = function(srf, logger) {
|
||||
|
||||
const initLocals = (req, res, next) => {
|
||||
const callId = req.get('Call-ID');
|
||||
|
||||
/* if we are drying up calls prior to scale-in, reject new INVITEs so the
|
||||
sender fails over to another SBC; allow INVITE with Replaces through
|
||||
since it targets a call already in progress on this server */
|
||||
if (srf.locals.dryUpCalls && !req.has('Replaces')) {
|
||||
logger.info({callId}, 'rejecting INVITE with 503 as we are drying up calls before scale-in');
|
||||
return res.send(503);
|
||||
}
|
||||
|
||||
req.locals = req.locals || {callId};
|
||||
req.locals.nudge = 0;
|
||||
|
||||
@@ -130,7 +139,8 @@ module.exports = function(srf, logger) {
|
||||
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
|
||||
return res.send(404, 'Number Not Provisioned');
|
||||
}
|
||||
logger.info({gateway}, 'identifyAccount: incoming call from gateway');
|
||||
const {register_password, ...gatewayForLog} = gateway;
|
||||
logger.info({gateway: gatewayForLog}, 'identifyAccount: incoming call from gateway');
|
||||
const appSidHeader = req.get('x-application-sid');
|
||||
if (appSidHeader && appSidHeader == application_sid) {
|
||||
logger.info({callId}, 'Loop Detected, x-application-sid header on incoming call matches applicationSid');
|
||||
|
||||
Generated
+1165
-856
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "0.9.5",
|
||||
"version": "0.9.11",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
"node": ">= 20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"sip",
|
||||
@@ -30,19 +30,19 @@
|
||||
"@aws-sdk/client-sns": "^3.549.0",
|
||||
"@babel/helpers": "^7.26.10",
|
||||
"@jambonz/db-helpers": "^0.9.18",
|
||||
"@jambonz/digest-utils": "^0.0.8",
|
||||
"@jambonz/digest-utils": "^0.0.9",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.18",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.21",
|
||||
"@jambonz/rtpengine-utils": "^0.4.4",
|
||||
"@jambonz/siprec-client-utils": "^0.2.10",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.10",
|
||||
"bent": "^7.3.12",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"debug": "^4.4.3",
|
||||
"drachtio-fn-b2b-sugar": "0.2.1",
|
||||
"drachtio-srf": "^5.0.5",
|
||||
"express": "^4.19.2",
|
||||
"drachtio-srf": "^5.0.27",
|
||||
"express": "^4.21.2",
|
||||
"pino": "^10.1.0",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"xml2js": "^0.6.2"
|
||||
|
||||
+28
-24
@@ -14,6 +14,8 @@ DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS clients;
|
||||
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
|
||||
DROP TABLE IF EXISTS lcr;
|
||||
@@ -66,8 +68,6 @@ DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS clients;
|
||||
|
||||
DROP TABLE IF EXISTS voip_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
@@ -132,6 +132,19 @@ application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
username VARCHAR(64),
|
||||
password VARCHAR(1024),
|
||||
allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (client_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE dns_records
|
||||
(
|
||||
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -191,6 +204,7 @@ tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to thi
|
||||
inbound_auth_username VARCHAR(64),
|
||||
inbound_auth_password VARCHAR(64),
|
||||
diversion VARCHAR(32),
|
||||
trunk_type ENUM('static_ip','auth','reg') NOT NULL DEFAULT 'static_ip',
|
||||
PRIMARY KEY (predefined_carrier_sid)
|
||||
);
|
||||
|
||||
@@ -405,7 +419,7 @@ register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||
register_status VARCHAR(4096),
|
||||
dtmf_type ENUM('rfc2833','tones','info') NOT NULL DEFAULT 'rfc2833',
|
||||
outbound_sip_proxy VARCHAR(255),
|
||||
trunk_type ENUM('static-ip','auth','registration') NOT NULL DEFAULT 'static-ip',
|
||||
trunk_type ENUM('static_ip','auth','reg') NOT NULL DEFAULT 'static_ip',
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
@@ -479,20 +493,6 @@ password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
|
||||
CREATE TABLE clients
|
||||
(
|
||||
client_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
username VARCHAR(64),
|
||||
password VARCHAR(1024),
|
||||
allow_direct_app_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_queue_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
allow_direct_user_calling BOOLEAN NOT NULL DEFAULT 1,
|
||||
voip_carrier_sid CHAR(36),
|
||||
PRIMARY KEY (client_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE applications
|
||||
(
|
||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
@@ -505,7 +505,7 @@ messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
app_json TEXT,
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(256),
|
||||
speech_synthesis_voice VARCHAR(256) DEFAULT 'en-US-Standard-C',
|
||||
speech_synthesis_label VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
@@ -582,6 +582,9 @@ ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERE
|
||||
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX client_sid_idx ON clients (client_sid);
|
||||
ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
@@ -702,6 +705,12 @@ ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_
|
||||
|
||||
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
CREATE INDEX idx_sip_gateways_inbound_carrier ON sip_gateways (inbound,voip_carrier_sid);
|
||||
|
||||
CREATE INDEX idx_sip_gateways_inbound_lookup ON sip_gateways (inbound,netmask,ipv4);
|
||||
|
||||
CREATE INDEX idx_sip_gateways_inbound_netmask ON sip_gateways (inbound,netmask);
|
||||
|
||||
CREATE INDEX voip_carrier_sid_idx ON sip_gateways (voip_carrier_sid);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
@@ -710,11 +719,6 @@ ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE INDEX client_sid_idx ON clients (client_sid);
|
||||
ALTER TABLE clients ADD CONSTRAINT account_sid_idxfk_13 FOREIGN KEY account_sid_idxfk_13 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE clients ADD FOREIGN KEY voip_carrier_sid_idxfk_4 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||
@@ -748,4 +752,4 @@ ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (devic
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
@@ -129,7 +129,7 @@ values ('acct-100', 'Account 100', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foob
|
||||
insert into voip_carriers (voip_carrier_sid, name, account_sid, service_provider_sid, trunk_type,
|
||||
requires_register, register_username, register_sip_realm, register_password, is_active)
|
||||
values ('4a7d1c8e-5f2b-4d9a-8e3c-6b5a9f1e4c7d', 'test-registration-trunk', 'ed649e33-e771-403a-8c99-1780eabbc803',
|
||||
'3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'registration', true, 'testuser',
|
||||
'3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'reg', true, 'testuser',
|
||||
'sip.carrier.example.com', 'testpass', true);
|
||||
|
||||
-- sip_gateway for outbound only (inbound will use ephemeral gateway from Redis)
|
||||
|
||||
Reference in New Issue
Block a user