路由、SIP Trunk 与计费模板
RustPBX 将线路控制拆分为「Trunk(承载)」与「Routing(策略)」两个层,计费模板则为线路和业务提供财务抽象。本篇从规划、配置到验证,完整呈现线路管理方法。
1. SIP Trunk 管理
1.1 建模思路
- Trunk = 线路资源:对应一个运营商、批发线路或内部 SBC,包含访问地址、鉴权方式、支持编码。
- 多 Trunk 编排:可针对成本、地域或冗余需求配置多个 Trunk,并在路由策略中按优先级或轮询使用。
1.2 创建步骤
- 控制台「SIP Trunk → 新建」,填写名称、SIP URI(
sip:host:port)。 - 选择鉴权方式:
- IP 信任:配置运营商出口 IP 与允许的编解码。
- 账号密码:输入
username/password,可启用 TLS/SRTP。
- 配置运行细节:
codec列表、max_calls并发限制、max_cps每秒呼叫速率限制、允许的inbound_hosts、方向(direction)及备份目的地backup_dest。 - Trunk 速率限制:
max_cps字段强制执行每条 Trunk 的每秒呼叫数上限。超过限制时,新的 INVITE 将返回 SIP 503(服务不可用)。max_calls控制并发、max_cps控制突发,两者独立生效。 - Trunk 健康监控:代理会定期向每条 Trunk 发送 SIP OPTIONS 探活。连续探活失败的 Trunk 会被自动标记为降级,路由决策可跳过它。通过 Diagnostics → Trunks 监控健康状态。通话记录同时会采集该 Trunk 应答后的媒体质量(丢包率、抖动、RTT),运营商质量劣化会直接体现在 CDR 中。
- SDES-SRTP(RTP/SAVP):代理自动识别运营商的 SDES-SRTP offer 并以同样方式应答——入向
RTP/SAVP+a=crypto会得到RTP/SAVP应答,媒体锚定时加密镜像到出局 SIP 腿,无需专门的 Trunk 开关。 - 录音与媒体代理覆写:每条 Trunk 可设置
recording.enabled和media_mode覆盖全局策略,TOML 字段见下方 GitOps 示例。 - 逐 Trunk 网络绑定(0.5+):overlay 网络(Tailscale/WireGuard、多 WAN)部署可通过
external_ip/bind_ip为每条 Trunk 覆盖 RTP 宣告地址与本地绑定地址,或引用主配置中的具名[[network_profile]];Trunk 级配置优先于 profile/全局。 - 自定义头透传(0.5+):通过
header_passthrough控制原 INVITE 的哪些自定义头转发到该 Trunk 的出站 INVITE——mode = "all" | "whitelist" | "blacklist" | "x_only"。标准 SIP 头永不转发。默认不向外部 Trunk 转发任何自定义头(内部目标始终全部转发,除非路由覆写)。x_only模式适用于 CCF 类外呼元数据 Trunk。 - ICE-lite(0.5+):对严格 full-ICE 对端(如 Microsoft Teams Direct Routing)可设置
ice_lite = true——代理在纯 RTP 腿上以 ICE-lite 应答。 - 保存后系统生成
trunk_id,可在路由或计费中引用。
1.3 文件配置
若使用 GitOps,可在 config/trunks/*.toml 定义:
name = "carrier-a"
uri = "sip:1.2.3.4:5060"
auth = { type = "ip", cidr = ["1.2.3.4/32"] }
codecs = ["g711a", "g729", "opus"]
concurrency_limit = 200
max_cps = 50
# 可选:覆盖全局录音策略(按 Trunk)
[recording]
enabled = true
auto_start = true
type = "local"
# 可选:覆盖媒体代理模式(按 Trunk)
# 取值:"auto"(默认)、"none"、"bypass"、"all"、"nat"
# media_mode = "auto"
# 可选:逐 Trunk 网络覆盖(overlay 网络 / 多 WAN)
# external_ip = "100.64.10.1"
# bind_ip = "100.64.10.2"
# 可选:向该 Trunk 的出站 INVITE 转发自定义头
# header_passthrough = { mode = "x_only" } # 仅 X- 前缀头
# header_passthrough = { mode = "whitelist", whitelist = ["X-Customer-Id"] }
# 可选:为严格 full-ICE 对端启用 ICE-lite(Teams Direct Routing)
# ice_lite = true
修改后需执行 Reload(详见《诊断工具》)。
2. Routing 策略
2.1 规则结构
| 字段 | 说明 |
|---|---|
direction | inbound/outbound,区分入站与出站场景 |
matchers | 主叫、被叫、时间、地区、业务标签等条件 |
actions | 调用分机、队列、IVR、外呼 Trunk、播放提示等 |
fallback | 主动作失败时的备选策略 |
2.2 配置流程
- 梳理入口:按 DID、SIP URI、外呼目的地整理业务入口,并与 Trunk 的
direction、inbound_hosts保持一致。 - 编写规则:在控制台或
config/routes/*.toml中设置match字段,可匹配from.*、to.*、request_uri.*以及指定 SIP header;支持正则表达式。 - 绑定动作:
action.dest用于转发到一个或多个 Trunk,action.queue/action.ivr对应队列或 IVR 文件,action.reject则直接返回 SIP 4xx/5xx。 - 多级策略:利用
priority和source_trunks(或source_trunk_ids)对不同入口分层;若主 Trunk 不可用,可在dest中列出备份次序。
示例:
[[routes]]
name = "vip-outbound"
direction = "outbound"
matchers = { callee_prefix = ["0086", "+86"], extension_group = "vip" }
actions = [
{ type = "send_trunk", trunk_id = "carrier-a", timeout = 30 },
{ type = "send_trunk", trunk_id = "carrier-b", timeout = 30 }
]
fallback = { type = "play_prompt", prompt = "vip_no_route.wav" }
2.3 变更发布
- 保存草稿后务必执行 Reload,使最新路由生效。
- 在 Diagnostics → Routing 中使用 Evaluate,分别对
runtime与database数据集进行验证,确认即将发布的规则逻辑正确。 - 结合
examples/voice_demo.rs或内置 Web Dialer 做真实呼叫测试,并在 CDR 中核对计费。
2.4 HTTP 动态路由
当路由决策需要依赖外部系统(CRM、实时定价、风控评分)时,可配置 HTTP 路由器:
[proxy.http_router]
url = "https://api.example.com/route"
headers = { Authorization = "Bearer token123" }
fallback_to_static = true
timeout_ms = 300
每次 INVITE 到达时,RustPBX 将呼叫详情以 JSON 格式发送到配置的 URL。外部服务返回路由决策(选择 Trunk、拒绝或延迟)。当 fallback_to_static = true 时,HTTP 端点不可用或未返回决策时,回退到 config/routes/*.toml 中的静态规则。
外服返回的决策还可以覆写出站腿的主叫身份。各插件的路由贡献与核心规则在同一套运行时序中评估:控制台的路由栈页面可集中查看 wholesale 与 CC 的贡献,并支持通过 PATCH API 调整顺序而无需重启。
2.5 紧急号码路由
紧急呼叫绕过常规路由,直接发送到指定 Trunk:
[proxy.emergency]
enabled = true
numbers = ["110", "119", "120", "911"]
emergency_trunk = "emergency-carrier"
当分机拨打列表中的号码时,代理立即路由到 emergency_trunk,不评估任何静态或 HTTP 路由规则。
2.6 DID 号码池
对于持有入局号码段的部署场景,RustPBX 可使用最少使用策略从号码池中分配号码。可按 Trunk 或全局配置号码池,以均匀分配入局容量。当呼叫到达未分配号码时,代理会从号码池目标组中选择负载最低的分机。
2.7 位置服务 Webhook
位置服务(将 SIP URI 解析为已注册的联系地址)可通过 Webhook 将注册变更通知外部系统:
[proxy.locator_webhook]
url = "https://hooks.example.com/locator"
events = ["register", "unregister"]
headers = { X-API-Key = "secret" }
timeout_ms = 5000
适用于将注册状态同步到外部在线状态服务器、CRM 系统或自定义仪表盘。
3. 计费模板
RustPBX 通过 models/bill_template.rs 定义计费模板,可在 UI 中以可视化方式管理。
3.1 设计要点
- 费率结构:支持按秒计费、分段计费、最低消费、峰谷平价差。
- 币种与税率:可自定义币种、税率,方便与财务系统对接。
- 绑定入口:模板可在 routing、queue、extension 模块中引用,实现“策略+计费“一体化。
3.2 示例
[bill_template.vip]
currency = "CNY"
billing_step = 6
free_seconds = 60
rates = [
{ prefix = "0086", price = 0.05 },
{ prefix = "001", price = 0.15 }
]
3.3 校验与对账
- 使用 Call Records 导出 CDR,与计费模板中的费率核对。
- Diagnostics → Billing 检查器可快速发现缺失模板或匹配失败的通话。
- 若需自定义分账,可结合
addons/wholesale插件扩展。
4. 最佳实践
- 多活线路:为不同运营商创建独立 Trunk,并在路由中设置优先级+失败转移,提高成功率。
- 差异化计费:一个路由可根据业务标签切换计费模板,实现 VIP/普通用户的不同费率。
- 灰度变更:在 routes 文件中添加
enabled = false字段进行预配置,Reload 后通过控制台手动启用,实现快速切换。 - 合规审计:所有 Trunk、Routing、Billing 的调整都建议走 PR/审批,配合 Git 记录。