SBC Complete Example

This section demonstrates a typical SBC deployment scenario: an enterprise PBX interconnected with carriers through an SBC.

Scenario Description

Enterprise Internal Network:

  • PBX subnet: 192.168.1.0/24
  • Extensions: 1000-1999
  • SIP server: 192.168.1.10:5060

SBC:

  • Internal IP: 192.168.1.100
  • Public IP: 203.0.113.100
  • Exposes SIP port 5060 and HTTP 8080

Carriers:

  • Carrier A: 198.51.100.10:5060 (primary)
  • Carrier B: 198.51.100.20:5060 (backup)

Requirements:

  1. Topology hiding: Carriers can only see the SBC address
  2. Outbound routing: Calls starting with 9 go to Carrier A, failover to Carrier B
  3. Inbound control: Only accept inbound calls from carrier IPs
  4. Number rewrite: Strip prefix 9 from outbound calls, prepend area code 0571
  5. Security protection: Limit CPS, prevent DoS

Step 1: config.toml

http_addr = "0.0.0.0:8080"
database_url = "sqlite://rustpbx.sqlite3"
external_ip = "203.0.113.100"

[proxy]
addr = "0.0.0.0"
udp_port = 5060
modules = ["acl", "auth", "registrar", "call"]
media_proxy = "all"
addons = ["sbc"]
max_concurrency = 500

[proxy.dos]
enabled = true
max_cps_per_ip = 20
max_concurrent_per_ip = 50
scan_detection = true

[console]
session_secret = "sbc-console-secret"

Step 2: Trunk Configuration

config/trunks/carriers.toml:

[[trunk]]
name = "carrier-a"
dest = "sip:198.51.100.10:5060"
direction = "outbound"
codec = ["pcmu", "pcma", "g729"]
max_calls = 200

[trunk.auth]
username = "ent_acct"
password = "ent_pass"

[trunk.health_check]
enabled = true
interval_secs = 30
timeout_secs = 5

[[trunk]]
name = "carrier-b"
dest = "sip:198.51.100.20:5060"
direction = "outbound"
codec = ["pcmu", "pcma"]
max_calls = 100

[trunk.auth]
username = "ent_acct_b"
password = "ent_pass_b"

[[trunk]]
name = "inbound-trunk"
direction = "inbound"
inbound_hosts = ["198.51.100.10", "198.51.100.20"]
codec = ["pcmu", "pcma", "g729"]
max_calls = 300

Step 3: Route Configuration

config/routes/outbound.toml:

[[route]]
name = "outbound-via-a"
action = "forward"
trunk = "carrier-a"

[route.match]
from_user = "^(?=.{4}$)"   # 4-digit extension
to_user = "^9(.*)"          # outbound starts with 9

[route.rewrite]
to_user_strip = 1           # Strip prefix 9
to_user_prepend = "0571"    # Add area code

[[route]]
name = "outbound-via-b"
action = "forward"
trunk = "carrier-b"

[route.match]
from_user = "^(?=.{4}$)"
to_user = "^9(.*)"

[route.rewrite]
to_user_strip = 1
to_user_prepend = "0571"

Step 4: SBC JSON-RPC Configuration (Optional)

If you need dynamic routing via an external API:

config/sbc/sbc_jsonrpc.toml:

[[rules]]
name = "office-hours"
description = "Office hours routing"

[rules.match]
logic = "all"
conditions = [
  { field = "Direction", operator = "Equals", value = "inbound" },
]

[rules.upstream]
url = "http://192.168.1.50:3000/api/route"
method = "POST"
timeout_ms = 1000

[rules.response]
action_field = "action"
trunk_field = "trunk"
callee_rewrite_field = "callee"

Step 5: Start

docker run -d --name sbc \
  --network host \
  -v $(pwd)/config.toml:/app/config.toml \
  -v $(pwd)/config:/app/config \
  docker.cnb.cool/miuda.ai/rustpbx:latest

Step 6: Verification

6.1 Internal Extension Registration

Extension 1000 registers to 192.168.1.100:5060 (SBC internal address). The SBC proxies the registration to the internal PBX.

6.2 Outbound Call Test

Extension 1000 dials 9138000138000:

  1. Matches route outbound-via-a
  2. Number rewrite: 9138000138000 → strip 1 → 138000138000 → prepend 0571 → 0571138000138000
  3. Forwarded to carrier-a (198.51.100.10)
  4. RTP address in SDP is SBC public IP (203.0.113.100), topology hidden

6.3 Failover Test

  1. Disconnect carrier-a
  2. Health check marks DOWN after 3 consecutive failures
  3. Subsequent outbound calls automatically use carrier-b

6.4 Inbound Test

Call from carrier to enterprise number:

  1. Matches inbound-trunk (IP ACL passes)
  2. Forwarded to internal PBX
  3. RTP address in SDP rewritten by SBC, internal IP not exposed

6.5 Security Test

High-frequency calls from unauthorized IPs:

  1. DoS detection triggered, source IP temporarily blocked
  2. Requests exceeding CPS limit are rejected

Step 7: Daily Operations

OperationFrequencyLocation
Check trunk healthDailySBC → Dashboard
Validate configurationAfter changesSBC → Validation
Route simulationAfter changesSBC → Routes → Simulate
View active callsReal-timeDashboard
Review DoS logsDailySystem logs