SBC 完整案例

本节演示一个典型 SBC 部署场景:企业 PBX 通过 SBC 与运营商互联。

场景描述

企业内网:

  • PBX 网段:192.168.1.0/24
  • 分机:1000-1999
  • SIP 服务器:192.168.1.10:5060

SBC:

  • 内网 IP:192.168.1.100
  • 公网 IP:203.0.113.100
  • 暴露 SIP 端口 5060 和 HTTP 8080

运营商:

  • 运营商 A:198.51.100.10:5060(主用)
  • 运营商 B:198.51.100.20:5060(备用)

需求:

  1. 拓扑隐藏:运营商只能看到 SBC 地址
  2. 外呼路由:拨 9 开头走运营商 A,失败自动转运营商 B
  3. 入向控制:只接受运营商 IP 的入局呼叫
  4. 号码改写:外呼去掉前缀 9,加上区号 0571
  5. 安全防护:限制 CPS,防止 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:中继配置

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:路由配置

config/routes/outbound.toml:

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

[route.match]
from_user = "^(?=.{4}$)"   # 4位分机号
to_user = "^9(.*)"          # 9开头外呼

[route.rewrite]
to_user_strip = 1           # 去掉前缀 9
to_user_prepend = "0571"    # 加区号

[[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 配置(可选)

如需通过外部 API 动态路由:

config/sbc/sbc_jsonrpc.toml:

[[rules]]
name = "office-hours"
description = "工作时间路由"

[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:启动

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:验证

6.1 内部分机注册

分机 1000 注册到 192.168.1.100:5060(SBC 内网地址),SBC 代理注册到内部 PBX。

6.2 外呼测试

分机 1000 拨打 9138000138000:

  1. 匹配路由 outbound-via-a
  2. 号码改写:9138000138000 → strip 1 → 138000138000 → prepend 0571 → 0571138000138000
  3. 转发到 carrier-a (198.51.100.10)
  4. SDP 中 RTP 地址为 SBC 公网 IP(203.0.113.100),拓扑隐藏

6.3 故障转移测试

  1. 断开 carrier-a 连接
  2. 健康检查 3 次失败后标记 DOWN
  3. 后续外呼自动使用 carrier-b

6.4 入向测试

从运营商发起呼叫到企业号码:

  1. 匹配 inbound-trunk(IP ACL 通过)
  2. 转发到内部 PBX
  3. SDP 中 RTP 地址经 SBC 改写,内网 IP 不暴露

6.5 安全测试

从非法 IP 发起高频呼叫:

  1. DoS 检测触发,源 IP 被临时封锁
  2. 超过 CPS 限制的请求被拒绝

Step 7:日常运维

操作频率位置
查看中继健康每天SBC → Dashboard
校验配置变更后SBC → 校验
路由模拟变更后SBC → 路由 → 模拟
查看活跃通话实时Dashboard
审查 DoS 日志每天系统日志