路由、SIP Trunk 与计费模板

RustPBX 将线路控制拆分为「Trunk(承载)」与「Routing(策略)」两个层,计费模板则为线路和业务提供财务抽象。本篇从规划、配置到验证,完整呈现线路管理方法。

1. SIP Trunk 管理

1.1 建模思路

  • Trunk = 线路资源:对应一个运营商、批发线路或内部 SBC,包含访问地址、鉴权方式、支持编码。
  • 多 Trunk 编排:可针对成本、地域或冗余需求配置多个 Trunk,并在路由策略中按优先级或轮询使用。

1.2 创建步骤

  1. 控制台「SIP Trunk → 新建」,填写名称、SIP URI(sip:host:port)。
  2. 选择鉴权方式:
    • IP 信任:配置运营商出口 IP 与允许的编解码。
    • 账号密码:输入 username/password,可启用 TLS/SRTP。
  3. 配置运行细节:codec 列表、max_calls 并发限制、max_cps 每秒呼叫速率限制、允许的 inbound_hosts、方向(direction)及备份目的地 backup_dest。
  4. Trunk 速率限制:max_cps 字段强制执行每条 Trunk 的每秒呼叫数上限。超过限制时,新的 INVITE 将返回 SIP 503(服务不可用)。max_calls 控制并发、max_cps 控制突发,两者独立生效。
  5. Trunk 健康监控:代理会定期向每条 Trunk 发送 SIP OPTIONS 探活。连续探活失败的 Trunk 会被自动标记为降级,路由决策可跳过它。通过 Diagnostics → Trunks 监控健康状态。通话记录同时会采集该 Trunk 应答后的媒体质量(丢包率、抖动、RTT),运营商质量劣化会直接体现在 CDR 中。
  6. SDES-SRTP(RTP/SAVP):代理自动识别运营商的 SDES-SRTP offer 并以同样方式应答——入向 RTP/SAVP + a=crypto 会得到 RTP/SAVP 应答,媒体锚定时加密镜像到出局 SIP 腿,无需专门的 Trunk 开关。
  7. 录音与媒体代理覆写:每条 Trunk 可设置 recording.enabled 和 media_mode 覆盖全局策略,TOML 字段见下方 GitOps 示例。
  8. 逐 Trunk 网络绑定(0.5+):overlay 网络(Tailscale/WireGuard、多 WAN)部署可通过 external_ip / bind_ip 为每条 Trunk 覆盖 RTP 宣告地址与本地绑定地址,或引用主配置中的具名 [[network_profile]];Trunk 级配置优先于 profile/全局。
  9. 自定义头透传(0.5+):通过 header_passthrough 控制原 INVITE 的哪些自定义头转发到该 Trunk 的出站 INVITE——mode = "all" | "whitelist" | "blacklist" | "x_only"。标准 SIP 头永不转发。默认不向外部 Trunk 转发任何自定义头(内部目标始终全部转发,除非路由覆写)。x_only 模式适用于 CCF 类外呼元数据 Trunk。
  10. ICE-lite(0.5+):对严格 full-ICE 对端(如 Microsoft Teams Direct Routing)可设置 ice_lite = true——代理在纯 RTP 腿上以 ICE-lite 应答。
  11. 保存后系统生成 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 策略

Flow

2.1 规则结构

RoutingFlow
字段说明
directioninbound/outbound,区分入站与出站场景
matchers主叫、被叫、时间、地区、业务标签等条件
actions调用分机、队列、IVR、外呼 Trunk、播放提示等
fallback主动作失败时的备选策略

2.2 配置流程

  1. 梳理入口:按 DID、SIP URI、外呼目的地整理业务入口,并与 Trunk 的 direction、inbound_hosts 保持一致。
  2. 编写规则:在控制台或 config/routes/*.toml 中设置 match 字段,可匹配 from.*、to.*、request_uri.* 以及指定 SIP header;支持正则表达式。
  3. 绑定动作:action.dest 用于转发到一个或多个 Trunk,action.queue/action.ivr 对应队列或 IVR 文件,action.reject 则直接返回 SIP 4xx/5xx。
  4. 多级策略:利用 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 变更发布

  1. 保存草稿后务必执行 Reload,使最新路由生效。
  2. 在 Diagnostics → Routing 中使用 Evaluate,分别对 runtime 与 database 数据集进行验证,确认即将发布的规则逻辑正确。
  3. 结合 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 设计要点

BillingStructure
  • 费率结构:支持按秒计费、分段计费、最低消费、峰谷平价差。
  • 币种与税率:可自定义币种、税率,方便与财务系统对接。
  • 绑定入口:模板可在 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 校验与对账

  1. 使用 Call Records 导出 CDR,与计费模板中的费率核对。
  2. Diagnostics → Billing 检查器可快速发现缺失模板或匹配失败的通话。
  3. 若需自定义分账,可结合 addons/wholesale 插件扩展。

4. 最佳实践

  • 多活线路:为不同运营商创建独立 Trunk,并在路由中设置优先级+失败转移,提高成功率。
  • 差异化计费:一个路由可根据业务标签切换计费模板,实现 VIP/普通用户的不同费率。
  • 灰度变更:在 routes 文件中添加 enabled = false 字段进行预配置,Reload 后通过控制台手动启用,实现快速切换。
  • 合规审计:所有 Trunk、Routing、Billing 的调整都建议走 PR/审批,配合 Git 记录。