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和 HTTP8080
运营商:
- 运营商 A:
198.51.100.10:5060(主用) - 运营商 B:
198.51.100.20:5060(备用)
需求:
- 拓扑隐藏:运营商只能看到 SBC 地址
- 外呼路由:拨 9 开头走运营商 A,失败自动转运营商 B
- 入向控制:只接受运营商 IP 的入局呼叫
- 号码改写:外呼去掉前缀 9,加上区号 0571
- 安全防护:限制 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:
- 匹配路由
outbound-via-a - 号码改写:
9138000138000→ strip 1 →138000138000→ prepend 0571 →0571138000138000 - 转发到 carrier-a (
198.51.100.10) - SDP 中 RTP 地址为 SBC 公网 IP(
203.0.113.100),拓扑隐藏
6.3 故障转移测试
- 断开 carrier-a 连接
- 健康检查 3 次失败后标记 DOWN
- 后续外呼自动使用 carrier-b
6.4 入向测试
从运营商发起呼叫到企业号码:
- 匹配 inbound-trunk(IP ACL 通过)
- 转发到内部 PBX
- SDP 中 RTP 地址经 SBC 改写,内网 IP 不暴露
6.5 安全测试
从非法 IP 发起高频呼叫:
- DoS 检测触发,源 IP 被临时封锁
- 超过 CPS 限制的请求被拒绝
Step 7:日常运维
| 操作 | 频率 | 位置 |
|---|---|---|
| 查看中继健康 | 每天 | SBC → Dashboard |
| 校验配置 | 变更后 | SBC → 校验 |
| 路由模拟 | 变更后 | SBC → 路由 → 模拟 |
| 查看活跃通话 | 实时 | Dashboard |
| 审查 DoS 日志 | 每天 | 系统日志 |