mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
769526be24 | ||
|
|
8448e003f6 | ||
|
|
f6e071f31e | ||
|
|
54d9044937 | ||
|
|
bcc089058f | ||
|
|
1a09a13cfa | ||
|
|
b311d4af48 | ||
|
|
0fe8843729 | ||
|
|
0b0e37020c | ||
|
|
01cde07117 | ||
|
|
31b4534878 | ||
|
|
0c6365a8df | ||
|
|
def2cb2be2 | ||
|
|
baaee6f063 | ||
|
|
d2f32229a2 | ||
|
|
38f2d27246 |
@@ -2,16 +2,10 @@ name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `master` as Docker `latest` image.
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Publish `v1.2.3` tags as releases.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: sbc-inbound
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
push:
|
||||
@@ -20,32 +14,41 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build image
|
||||
run: docker build . --file Dockerfile --tag $IMAGE_NAME
|
||||
|
||||
- name: Log into registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Push image
|
||||
- name: prepare tag
|
||||
id: prepare_tag
|
||||
run: |
|
||||
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
|
||||
IMAGE_ID=jambonz/sbc-inbound
|
||||
|
||||
# Change all uppercase to lowercase
|
||||
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
|
||||
# Strip git ref prefix from version
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
|
||||
# Strip "v" prefix from tag name
|
||||
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
|
||||
# Use Docker `latest` tag convention
|
||||
[ "$VERSION" == "main" ] && VERSION=latest
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
|
||||
echo IMAGE_ID=$IMAGE_ID
|
||||
echo VERSION=$VERSION
|
||||
echo "image_id=$IMAGE_ID" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
|
||||
docker push $IMAGE_ID:$VERSION
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.prepare_tag.outputs.image_id }}:${{ steps.prepare_tag.outputs.version }}
|
||||
build-args: |
|
||||
GITHUB_REPOSITORY=$GITHUB_REPOSITORY
|
||||
GITHUB_REF=$GITHUB_REF
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 node:18.14.1-alpine3.16 as base
|
||||
FROM --platform=linux/amd64 node:18.15-alpine3.16 as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
# sbc-inbound 
|
||||
|
||||
This application provides a part of the SBC (Session Border Controller) functionality of jambonz. It handles incoming INVITE requests from carrier sip trunks or from sip devices and webrtc applications. SIP INVITEs from known carriers are allowed in, while INVITEs from sip devices are challenged to authenticate. SIP traffic that is allowed in is sent on to a jambonz application server in a private subnet.
|
||||
This application provides a part of the SBC (Session Border Controller) functionality of jambonz platform. It handles incoming INVITE requests from carrier sip trunks or from sip devices and webrtc applications. SIP INVITEs from known carriers are allowed in, while INVITEs from sip devices are challenged to authenticate. SIP traffic that is allowed in is sent on to a jambonz application server in a private subnet.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is provided via the [npmjs config](https://www.npmjs.com/package/config) package. The following elements make up the configuration for the application:
|
||||
Configuration is provided via environment variables:
|
||||
|
||||
| variable | meaning | required?|
|
||||
|----------|----------|---------|
|
||||
|DRACHTIO_HOST| ip address of drachtio server (typically '127.0.0.1')|yes|
|
||||
|DRACHTIO_PORT| listening port of drachtio server for control connections (typically 9022)|yes|
|
||||
|DRACHTIO_SECRET| shared secret|yes|
|
||||
|HTTP_PORT| http listen port |no|
|
||||
|JAMBONES_LOGLEVEL| log level for application, 'info' or 'debug'|no|
|
||||
|JAMBONES_MYSQL_HOST| mysql host|yes|
|
||||
|JAMBONES_MYSQL_PORT| mysql port |no|
|
||||
|JAMBONES_MYSQL_USER| mysql username|yes|
|
||||
|JAMBONES_MYSQL_PASSWORD| mysql password|yes|
|
||||
|JAMBONES_MYSQL_DATABASE| mysql data|yes|
|
||||
|JAMBONES_MYSQL_CONNECTION_LIMIT| mysql connection limit |no|
|
||||
|DTMF_LISTEN_PORT| DTMF listening port |no|
|
||||
|JAMBONES_NG_PROTOCOL| rtpengine NG protocol |no|
|
||||
|RTPENGINE_PORT| rtpengine port |no|
|
||||
|JAMBONES_CLUSTER_ID| cluster id |no|
|
||||
|JAMBONES_NETWORK_CIDR| CIDR of private network that feature server is running in (e.g. '172.31.0.0/16')|yes|
|
||||
|JAMBONES_REDIS_HOST| redis host|yes|
|
||||
|JAMBONES_REDIS_PORT|redis port|no|
|
||||
|JAMBONES_RTPENGINES| commas-separated list of ip:ng-port for rtpengines (e.g. '172.31.32.10:22222')|no|
|
||||
|JAMBONES_TIME_SERIES_HOST| influxdb host |yes|
|
||||
|JAMBONES_TIME_SERIES_PORT| influxdb port |no|
|
||||
|JAMBONES_RECORD_ALL_CALLS| enable auto record calls, 'yes' or 'no' |no|
|
||||
|K8S| service running as kubernetes service |no|
|
||||
|K8S_RTPENGINE_SERVICE_NAME| rtpengine service name(required for K8S) |no|
|
||||
|K8S_FEATURE_SERVER_SERVICE_NAME| feature server service name(required for K8S) |no|
|
||||
|
||||
##### drachtio server location
|
||||
```
|
||||
{
|
||||
|
||||
@@ -3,10 +3,12 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_USER &&
|
||||
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env vars');
|
||||
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
||||
assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
|
||||
assert.ok(process.env.JAMBONES_NETWORK_CIDR || process.env.K8S, 'missing JAMBONES_NETWORK_CIDR env var');
|
||||
|
||||
const Srf = require('drachtio-srf');
|
||||
const srf = new Srf('sbc-inbound');
|
||||
const opts = Object.assign({
|
||||
@@ -50,6 +52,7 @@ const {
|
||||
queryCallLimits
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
port: process.env.JAMBONES_MYSQL_PORT || 3306,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||
@@ -63,7 +66,7 @@ const {
|
||||
removeFromSet,
|
||||
incrKey,
|
||||
decrKey} = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
host: process.env.JAMBONES_REDIS_HOST,
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
@@ -247,7 +250,8 @@ if (process.env.K8S || process.env.HTTP_PORT) {
|
||||
if ('test' !== process.env.NODE_ENV) {
|
||||
/* update call stats periodically */
|
||||
setInterval(() => {
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
|
||||
stats.gauge('sbc.sip.calls.count', activeCallIds.size,
|
||||
['direction:inbound', `instance_id:${process.env.INSTANCE_ID || 0}`]);
|
||||
}, 20000);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -223,7 +223,7 @@ class CallSession extends Emitter {
|
||||
if (this.req.locals.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.locals.application_sid});
|
||||
}
|
||||
else if (this.req.authorization) {
|
||||
if (this.req.authorization) {
|
||||
if (this.req.authorization.grant && this.req.authorization.grant.application_sid) {
|
||||
Object.assign(headers, {'X-Application-Sid': this.req.authorization.grant.application_sid});
|
||||
}
|
||||
@@ -293,7 +293,6 @@ class CallSession extends Emitter {
|
||||
} catch (err) {
|
||||
this.rtpEngineResource.destroy().catch((err) => this.logger.info({err}, 'Error destroying rtpe after failure'));
|
||||
this.activeCallIds.delete(this.req.get('Call-ID'));
|
||||
this.stats.gauge('sbc.sip.calls.count', this.activeCallIds.size);
|
||||
if (err instanceof SipError) {
|
||||
const tags = ['accepted:no', `sipStatus:${err.status}`, `originator:${this.req.locals.originator}`];
|
||||
this.stats.increment('sbc.terminations', tags);
|
||||
@@ -612,7 +611,8 @@ Duration=${payload.duration} `
|
||||
direction,
|
||||
sdp: offeredSdp,
|
||||
};
|
||||
if (reason && opts.flags && !opts.flags.includes('reset')) opts.flags.push('reset');
|
||||
// Dont reset ICE - causes audiocodes webrtrc to fail with "missing ice-ufrag and ice-pwd in re-invite"
|
||||
// if (reason && opts.flags && !opts.flags.includes('reset')) opts.flags.push('reset');
|
||||
|
||||
let response = await this.offer(opts);
|
||||
if ('ok' !== response.result) {
|
||||
|
||||
+7
-7
@@ -137,12 +137,12 @@ 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 [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);
|
||||
const [a] = await pp.query(sqlAccountByRealm, [uri.host]);
|
||||
if (0 === a.length) return failure;
|
||||
return {
|
||||
fromCarrier: true,
|
||||
@@ -168,7 +168,7 @@ module.exports = (srf, logger) => {
|
||||
//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);
|
||||
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 {
|
||||
@@ -247,7 +247,7 @@ module.exports = (srf, logger) => {
|
||||
}
|
||||
else if (accountLevelGateways.length === 1) {
|
||||
const [accounts] = await pp.query('SELECT * from accounts where account_sid = ?',
|
||||
accountLevelGateways[0].account_sid);
|
||||
[accountLevelGateways[0].account_sid]);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: accountLevelGateways[0],
|
||||
@@ -262,11 +262,11 @@ module.exports = (srf, logger) => {
|
||||
- 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);
|
||||
[matches[0].service_provider_sid]);
|
||||
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);
|
||||
[matches[0].service_provider_sid]);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
gateway: matches[0],
|
||||
@@ -288,7 +288,7 @@ module.exports = (srf, logger) => {
|
||||
|
||||
/* 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);
|
||||
const [accounts] = await pp.query(sqlAccountBySid, [r[0].account_sid]);
|
||||
assert(accounts.length);
|
||||
return {
|
||||
fromCarrier: true,
|
||||
|
||||
Generated
+140
-3693
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sbc-inbound",
|
||||
"version": "v0.8.1",
|
||||
"version": "0.8.3",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -25,15 +25,15 @@
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.7.4",
|
||||
"@jambonz/db-helpers": "^0.8.1",
|
||||
"@jambonz/http-authenticator": "^0.2.2",
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/realtimedb-helpers": "^0.6.3",
|
||||
"@jambonz/realtimedb-helpers": "^0.7.2",
|
||||
"@jambonz/rtpengine-utils": "^0.4.3",
|
||||
"@jambonz/siprec-client-utils": "^0.2.4",
|
||||
"@jambonz/stats-collector": "^0.1.6",
|
||||
"@jambonz/siprec-client-utils": "^0.2.5",
|
||||
"@jambonz/stats-collector": "^0.1.8",
|
||||
"@jambonz/time-series": "^0.2.5",
|
||||
"aws-sdk": "^2.1261.0",
|
||||
"aws-sdk": "^2.1354.0",
|
||||
"bent": "^7.3.12",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
|
||||
Reference in New Issue
Block a user