**入站呼叫不振铃,99%的情况下不是媒体问题,而是信令在到达LiveKit之前就已经被丢弃了。** 媒体层(RTP)只有在SIP会话成功建立(INVITE得到200 OK并完成ACK)之后才会开始工作——如果呼叫根本没有在LiveKit的SIP栈中匹配到正确的Trunk和Dispatch Rule,手机端听到的就是持续的振铃,而LiveKit侧没有任何日志显示“呼叫已接听”。
本文按**信令、认证、SIP URI**三层递进排查,每一层都指向“呼叫在哪一步被静默丢弃”。
第一层:信令层——Dispatch Rule匹配失败
症状特征
呼叫在运营商侧显示“已发送”,但LiveKit的Call Logs中**没有任何入站呼叫记录**,或者日志中出现 `DispatchNoRuleDrop` 、 `No SIP trunk matched` 。手机端持续振铃,但LiveKit侧从未收到有效的INVITE处理请求。
根因一:`inbound_numbers`填错了对象——最隐蔽的字段语义陷阱
这是LiveKit入站路由中最常见的单一错误。`inbound_numbers`在Dispatch Rule中的语义是**过滤主叫号码(From header)** ,而不是“这条规则服务于哪些DID”。当你把印度的被叫DID(+91号码)填入这个字段时,**每一个真实主叫的号码都不在列表中,规则被完全跳过**,呼叫进入 `DispatchNoRuleDrop` 分支。
**字段语义的精确区分**:
| 字段 | 所属对象 | 匹配的是 | 填DID的后果 |
|---|---|---|---|
| numbers | inbound trunk | 被叫号码(你的DID) | ✅ 正确,这是DID应该填的地方 |
| allowed_numbers | inbound trunk | 主叫号码(From) | ❌ 填DID会导致所有真实主叫被拒 |
| inbound_numbers | dispatch rule | 主叫号码(From) | ❌ 填DID会导致规则完全失效 |
**快速验证方法**:创建一个**没有任何过滤条件的Dispatch Rule**——`inbound_numbers`留空,`trunk_ids`留空(空匹配所有trunk),并从trunk中移除`allowed_numbers`,然后拨打一次测试呼叫。如果接通,问题就定位在过滤字段的配置上。
根因二:Trunk的`numbers`字段缺少正确的E.164格式
如果inbound trunk的`numbers`字段没有包含来电的实际被叫号码,LiveKit在SIP栈层面就会返回 `No SIP trunk matched` 。常见的格式错误包括:号码缺少`+`前缀、存在空格或分隔符、同一号码存在多个变体(如`+919876543210`和`919876543210`同时出现)。
**正确做法**:在trunk的`numbers`字段中,**每个DID只保留一个规范的E.164条目**(`+91XXXXXXXXXX`),删除所有其他变体。如果使用Telnyx,需要确认Destination Number Format设置为**+E.164**,否则前导`+`可能不被接受。
根因三:Dispatch Rule与Trunk没有正确关联
Dispatch Rule必须通过`trunk_ids`字段**显式关联到目标inbound trunk**,或者**省略`trunk_ids`以匹配所有trunk**。如果`trunk_ids`指向了一个不存在的ID,或指向了另一个trunk,当前trunk的呼叫就不会被任何规则匹配。
**排查命令**:
```bash
lk sip dispatch list
```
检查**每一条规则**的`trunk_ids`,而不仅仅是“你认为相关的那条”。一个空的`trunk_ids`通配规则可能与你显式创建的规则产生冲突,导致匹配失败而非选中胜出者。
第二层:认证层——IP白名单与Digest的错配
症状特征
呼叫在LiveKit的SIP日志中**有INVITE记录**,但返回**403 Forbidden**或**401 Unauthorized**,然后呼叫被丢弃。手机端可能听到忙音或直接断线,而不是持续振铃。
根因一:运营商不支持Digest,而LiveKit侧没有启用IP白名单
这是印度市场最常见的认证层故障。**Telnyx、Twilio Elastic SIP Trunking等运营商在入站呼叫中不发送Digest凭据**——它们期望通过源IP地址来认证。但LiveKit的`allowed_addresses`(入站trunk的IP白名单)在默认情况下**需要手动向LiveKit申请启用**,并非所有项目默认开启。
**后果**:运营商发来INVITE,LiveKit尝试Digest认证,运营商不提供凭据,LiveKit拒绝呼叫。链路两端都“做对了自己该做的事”,但配置没有对齐。
**修复路径**:
1. 向LiveKit提交请求,**启用项目的`allowed_addresses`功能**(这是一个项目级设置,启用后对该项目下所有现有和未来trunk生效)。
2. 在inbound trunk中配置`allowed_addresses`,填入运营商的**信令IP或CIDR范围**。
3. 确保运营商侧的IP与LiveKit白名单中的条目完全匹配,**包括UDP和TCP两种传输方式**(白名单过滤不区分传输协议)。
根因二:误以为LiveKit支持SIP REGISTER
**LiveKit SIP不支持REGISTER方式注册**。它只支持**INVITE层面的Digest挑战**:第一通INVITE收到401/407挑战,客户端在重试的INVITE中携带认证响应。如果你的运营商期望LiveKit像传统SIP终端一样先REGISTER再收呼叫,这条路径从一开始就行不通——必须切换到IP白名单或INVITE Digest模式。
第三层:SIP URI层——印度区域与端点格式
症状特征
运营商侧的日志显示呼叫**已发送到某个SIP URI**,但LiveKit的Telephony Dashboard中**完全没有该呼叫的记录**。呼叫可能到达了LiveKit的错误区域,或URI格式本身不被运营商接受。
根因一:印度呼叫没有使用区域固定端点
印度TRAI的监管要求意味着,印度PSTN相关的呼叫**必须锚定在印度境内**。如果运营商将呼叫发往LiveKit的**全局端点**(如`.sip.livekit.cloud`),呼叫可能被路由到离发起运营商最近的区域,**而不是印度区域**——这在媒体锚定合规上可能直接导致呼叫被运营商侧丢弃,或在LiveKit侧被当作“非印度”流量处理。
**印度的正确入站端点格式**:
```
.india.sip.livekit.cloud
```
其中``是**项目ID去掉`p_`前缀**后的子域名。例如,如果SIP URI是`sip:bwwn08a2m4o.sip.livekit.cloud`,印度区域端点就是`bwwn08a2m4o.india.sip.livekit.cloud`。
**运营商侧必须配置的传输协议**:将运营商的SIP trunk指向这个印度区域主机名,使用**TCP或TLS**,而不是UDP。Plivo的文档明确标注,对于印度呼叫,**必须为LiveKit项目启用region pinning**,这是监管要求。
根因二:URI中缺少正确的传输参数
印度区域的TLS URI格式应为:
```
sip:{project_id}.india.sip.livekit.cloud;transport=tls
```
使用`sip:`方案而非`sips:`,并显式声明`transport=tls`。如果运营商侧配置的URI缺少传输参数,或使用了错误的方案,INVITE可能被LiveKit的SIP边缘拒绝或无法路由到正确的处理节点。
根因三:运营商把Call SID放在了To user中
这是一个**运营商侧的URI构造错误**,但在LiveKit日志中表现为 `No SIP trunk matched` 。如果运营商在INVITE的To头中放入的是Call SID(而非被叫DID),LiveKit无法从To头中提取出正确的被叫号码来匹配trunk的`numbers`字段。
**排查方法**:在运营商侧或中间抓包工具(如`sngrep`)中检查INVITE的**To头**,确认其中是**E.164格式的DID**,而不是任何内部标识符。
三层排查决策树
| 现象 | 第一嫌疑层 | 优先检查 |
|---|---|---|
| LiveKit无任何入站日志 | SIP URI层 | 运营商是否指向.india.sip.livekit.cloud,传输协议是否为TCP/TLS |
| 日志出现No SIP trunk matched | 信令层 | Trunk的numbers格式;运营商INVITE的To头是否为DID |
| 日志出现DispatchNoRuleDrop | 信令层 | Dispatch Rule的inbound_numbers是否误填了DID |
| 403 Forbidden / 401 Unauthorized | 认证层 | allowed_addresses是否已启用并填入运营商IP;运营商是否支持Digest |
| 呼叫进入错误区域 | SIP URI层 | 是否使用印度区域端点,region pinning是否启用 |
总结
**LiveKit入站SIP“不振铃”的排查,必须从媒体层的思维中跳出来。** 振铃是信令层成功建立会话后的结果,而信令失败在LiveKit的设计中是**静默丢弃**而非显式报错。
**三层排查的核心逻辑**:
1. **信令层**:先确认Dispatch Rule的`inbound_numbers`没有误填DID,再确认Trunk的`numbers`是规范的E.164单一条目,最后确认Dispatch Rule与Trunk正确关联。
2. **认证层**:确认运营商是否需要IP白名单(印度主流运营商如Telnyx/Twilio通常需要),并确认LiveKit项目的`allowed_addresses`已启用且配置了运营商IP。
3. **SIP URI层**:印度呼叫**必须**使用`.india.sip.livekit.cloud`区域端点,传输协议使用TCP/TLS,并确认运营商侧的To头携带的是DID而非Call SID。
**最快的单次验证动作**:创建一个**无任何过滤条件的Dispatch Rule**(`inbound_numbers`留空、`trunk_ids`留空),从Trunk移除`allowed_numbers`,拨打一次测试呼叫。如果接通,问题就在过滤配置;如果不通,按URI层→认证层的顺序继续排查。