mirror of
https://github.com/jambonz/sbc-outbound.git
synced 2026-10-04 02:04:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffec713d62 | ||
|
|
6921c1cf31 | ||
|
|
37e4dea705 | ||
|
|
4fb5acaf40 | ||
|
|
865068f158 | ||
|
|
4f65b4b585 | ||
|
|
8ccd02aa1f | ||
|
|
0911032146 | ||
|
|
121ae75b39 | ||
|
|
57bc073ea3 | ||
|
|
84bce56766 | ||
|
|
d053b94e71 | ||
|
|
a50ed56b36 | ||
|
|
8fdcc8cbc4 |
@@ -34,6 +34,7 @@ build/Release
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
.vscode
|
||||
|
||||
examples/*
|
||||
CLAUDE.md
|
||||
@@ -29,9 +29,19 @@ Configuration is provided via environment variables:
|
||||
|JAMBONES_RTPENGINES| commans-separated list of ip:ng-port for rtpengines (e.g. '172.31.32.10:22222')|yes|
|
||||
|JAMBONES_TIME_SERIES_HOST| influxdb host |yes|
|
||||
|JAMBONES_RECORD_ALL_CALLS| enable auto record calls |no|
|
||||
|JAMBONES_CODEC_OFFER_WITH_ORDER| comma-separated codec list to use as the outbound offer toward the carrier; the original codecs from the feature server are stripped first (e.g. `'opus,PCMU,PCMA,telephone-event'`) |no|
|
||||
|JAMBONES_CODEC_TRANSCODE| comma-separated codec list that rtpengine should add to the outbound offer and transcode on the fly when the carrier selects them — used to support codecs the feature server does not speak (e.g. `'AMR-WB/16000'`). Requires an rtpengine build that includes the corresponding codec module. |no|
|
||||
|JAMBONES_ACCEPT_AND_TRANSCODE| comma-separated codec list to accept on the outbound leg and transcode to PCMU/PCMA toward the feature server |no|
|
||||
|JAMBONES_ACCEPT_G729| if set, accept G.729 on the outbound leg and transcode to PCMU/PCMA (shorthand for `JAMBONES_ACCEPT_AND_TRANSCODE=g729`) |no|
|
||||
|K8S| service running as kubernetes service |no|
|
||||
|K8S_RTPENGINE_SERVICE_NAME| rtpengine service name(required for K8S) |no|
|
||||
|
||||
#### Codec transcoding notes
|
||||
|
||||
`JAMBONES_CODEC_OFFER_WITH_ORDER` and `JAMBONES_CODEC_TRANSCODE` can be combined. When both are set, rtpengine first strips the original codecs from the SDP, then adds the codecs listed in `JAMBONES_CODEC_OFFER_WITH_ORDER` to the offer, and finally adds the codecs from `JAMBONES_CODEC_TRANSCODE` with transcoding enabled. When only `JAMBONES_CODEC_TRANSCODE` is set, the original codecs from the feature server are kept in the offer and the transcode codecs are appended.
|
||||
|
||||
Codecs that require licensed or optional rtpengine modules (AMR, AMR-WB, G.729, etc.) only work if rtpengine was compiled with support for them. Carrier-specific fmtp parameters (for example, `octet-align=1` for AMR-WB) can be appended to the codec name as needed (e.g. `AMR-WB/16000;octet-align=1`).
|
||||
|
||||
### running under pm2
|
||||
Typically, this application runs under [pm2](https://pm2.io) using an [ecosystem.config.js](https://pm2.keymetrics.io/docs/usage/application-declaration/) file similar to this:
|
||||
```js
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ICE": "force",
|
||||
"SDES": "off",
|
||||
"flags": ["generate mid", "SDES-no", "port latching"],
|
||||
"rtcp-mux": ["require"]
|
||||
"rtcp-mux": ["offer"]
|
||||
},
|
||||
"teams": {
|
||||
"transport-protocol": "RTP/SAVP",
|
||||
|
||||
+30
-4
@@ -237,6 +237,11 @@ class CallSession extends Emitter {
|
||||
this.logger.info(`sending call to registered user ${destUri}`);
|
||||
}
|
||||
else if (this.req.locals.target === 'forward') {
|
||||
if (process.env.JAMBONES_SIPS_FORWARD_SRTP && this.req.uri.startsWith('sips:')) {
|
||||
this.logger.info({uri: this.req.uri}, 'using SRTP/TLS for forwarded sips: call');
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, true, false, false);
|
||||
encryptedMedia = true;
|
||||
}
|
||||
uris = [{
|
||||
private_network: await isPrivateVoipNetwork(this.req.uri),
|
||||
uri: this.req.uri
|
||||
@@ -352,7 +357,8 @@ class CallSession extends Emitter {
|
||||
* like a rare use case -- encryption is usually an all or nothing requirement.
|
||||
*/
|
||||
this.logger.info({u}, `using SRTP for outbound call, pad crypto: ${o.pad_crypto ? 'yes' : 'no'}`);
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, true, o.pad_crypto, true);
|
||||
// eslint-disable-next-line max-len
|
||||
this.rtpEngineOpts = makeRtpEngineOpts(this.req, false, true, o.pad_crypto, true, o.remove_ice, o.dtls_off);
|
||||
encryptedMedia = true;
|
||||
}
|
||||
});
|
||||
@@ -659,7 +665,21 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if ('abandonded' !== err.message) this.logger.error({err}, `Error setting up outbound call to: ${uris}`);
|
||||
if ('abandoned' !== err.message) this.logger.error({err}, `Error setting up outbound call to: ${uris}`);
|
||||
|
||||
/* the call count was incremented on 'init' but we bailed out before connecting (e.g. caller
|
||||
hung up before the B leg answered), so we must decrement it here - no failure response is
|
||||
sent on this path, so the res.once('end') safety net in middleware would not fire.
|
||||
nudgeCallCounts is a no-op if the count was already decremented elsewhere. */
|
||||
const {writeCallCount, writeCallCountSP, writeCallCountApp} = this.req.srf.locals;
|
||||
nudgeCallCounts(this.req, 'failure', {
|
||||
service_provider_sid: this.service_provider_sid,
|
||||
account_sid: this.account_sid,
|
||||
application_sid: this.application_sid,
|
||||
callId: this.req.locals.callId
|
||||
}, this.decrKey, {writeCallCountSP, writeCallCount, writeCallCountApp})
|
||||
.catch((err) => this.logger.error(err, 'Error decrementing call counts'));
|
||||
|
||||
this.emit('failed');
|
||||
this.srf.endSession(this.req);
|
||||
this.rtpEngineResource.destroy();
|
||||
@@ -886,8 +906,14 @@ Duration=${payload.duration} `
|
||||
process.env.JAMBONES_ACCEPT_G729 ? 'g729' : '';
|
||||
offerMedia = { ...offerMedia, ...(acceptCodecs && dlg.type === 'uac' &&
|
||||
{ codec: { mask: acceptCodecs, transcode: 'pcmu,pcma' } })};
|
||||
answerMedia = { ...answerMedia, ...(process.env.JAMBONES_CODEC_OFFER_WITH_ORDER && dlg.type === 'uac' &&
|
||||
{ codec: {offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','), strip: 'all' }})};
|
||||
answerMedia = { ...answerMedia, ...((process.env.JAMBONES_CODEC_OFFER_WITH_ORDER ||
|
||||
process.env.JAMBONES_CODEC_TRANSCODE) && dlg.type === 'uac' &&
|
||||
{ codec: {
|
||||
...(process.env.JAMBONES_CODEC_OFFER_WITH_ORDER &&
|
||||
{ offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','), strip: 'all' }),
|
||||
...(process.env.JAMBONES_CODEC_TRANSCODE &&
|
||||
{ transcode: process.env.JAMBONES_CODEC_TRANSCODE.split(',') })
|
||||
}})};
|
||||
let opts = {
|
||||
...this.rtpEngineOpts.common,
|
||||
...offerMedia,
|
||||
|
||||
+14
-1
@@ -5,7 +5,8 @@ const CIDRMatcher = require('cidr-matcher');
|
||||
const dns = require('dns');
|
||||
const sdpTransform = require('sdp-transform');
|
||||
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams) {
|
||||
// eslint-disable-next-line max-len
|
||||
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams, remove_ice = false, dtls_off = false) {
|
||||
const from = req.getParsedHeader('from');
|
||||
const rtpCopy = JSON.parse(JSON.stringify(rtpCharacteristics));
|
||||
const srtpCopy = JSON.parse(JSON.stringify(srtpCharacteristics));
|
||||
@@ -16,6 +17,14 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams
|
||||
}
|
||||
|
||||
const srtpOpts = teams ? srtpCopy['teams'] : srtpCopy['default'];
|
||||
|
||||
if (remove_ice) {
|
||||
srtpOpts.ICE = 'remove';
|
||||
}
|
||||
if (dtls_off) {
|
||||
srtpOpts.DTLS = 'off';
|
||||
}
|
||||
|
||||
const dstOpts = JSON.parse(JSON.stringify(dstIsUsingSrtp ? srtpOpts : rtpCopy));
|
||||
const srcOpts = JSON.parse(JSON.stringify(srcIsUsingSrtp ? srtpOpts : rtpCopy));
|
||||
|
||||
@@ -49,6 +58,10 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, padCrypto, teams
|
||||
offer: process.env.JAMBONES_CODEC_OFFER_WITH_ORDER.split(','),
|
||||
strip: 'all'
|
||||
}),
|
||||
...(process.env.JAMBONES_CODEC_TRANSCODE &&
|
||||
{
|
||||
transcode: process.env.JAMBONES_CODEC_TRANSCODE.split(',')
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Generated
+590
-503
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "sbc-outbound",
|
||||
"version": "0.9.5",
|
||||
"version": "0.9.7",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
"node": ">= 20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"sip",
|
||||
@@ -31,7 +31,7 @@
|
||||
"@jambonz/db-helpers": "^0.9.18",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.13",
|
||||
"@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",
|
||||
@@ -39,7 +39,7 @@
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.4.3",
|
||||
"drachtio-fn-b2b-sugar": "^0.2.1",
|
||||
"drachtio-srf": "^5.0.12",
|
||||
"drachtio-srf": "^5.0.21",
|
||||
"express": "^4.21.2",
|
||||
"pino": "^10.1.0",
|
||||
"sdp-transform": "^2.15.0"
|
||||
@@ -48,7 +48,7 @@
|
||||
"bent": "^7.3.12",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"tape": "^5.7.5"
|
||||
"nyc": "^17.1.0",
|
||||
"tape": "^5.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user