问题背景
公司新办公室有 60 多人,电话系统用的是本地 IP PBX(Asterisk/FreePBX),外线通过运营商的 SIP 中继接入。PBX 放在内网 192.168.10.20,出口是一台企业路由器做 NAT,宽带拨号上网。内部分机互打一直正常,外线电话由运营商 SIP 中继落地。
搬进新办公室后,外线通话开始出问题:电话能接通,但经常只有一方能听到声音;有些通话在 30 秒左右自动断掉。运营商说中继侧日志正常,PBX 厂商说配置没问题,问题卡在中间的网络设备上。
故障现象
- 外呼和外呼接入都能建立通话,但大约一半的通话只有单向语音:主叫能听到对方,对方听不到主叫,或者反过来。
- 部分通话在 30 秒左右自动断开,像是有一方没有收到后续的 SIP 消息。
- 内部分机互打、内部会议完全正常,问题只出现在经过外网 SIP 中继的通话上。
- 换用软电话、桌面话机、手机 SIP 客户端,现象一致,排除终端问题。
- PBX 抓包里能看到 RTP 发出去,但对端没有回包,或者回包发到了错误的地址/端口。
- 路由器上 SIP ALG 是默认开启的,之前的老办公室用的是另一台路由器,没有这个问题。
排查过程
第一步确认是信令还是媒体。在 PBX 上抓包,SIP 的 INVITE、200 OK、ACK 都能正常收发,说明信令链路是通的;但 RTP 只有单向,问题在媒体地址协商。再看 SDP 里的 c= 和 m= 字段,PBX 宣告的是内网地址 192.168.10.20 和 RTP 端口段,这本身没问题,NAT 设备应该负责把它转换成公网可达的地址。
第二步在路由器 WAN 侧抓包对比。经过路由器后,SDP 里的地址确实被改成了公网 IP,但端口和 Contact 头被改得前后不一致:INVITE 里的媒体端口被 ALG 换成了一个临时端口,而后续 200 OK 里的 Contact 又指向另一个地址。对端按其中一处地址发 RTP,结果发到了一个没人监听的端口,媒体自然只有单向。
第三步查路由器配置。登录路由器,发现“SIP ALG”默认开启,NAT 里也没有为 PBX 做任何端口映射。SIP ALG 的本意是帮 SIP 协议穿越 NAT:它会解析 SIP 报文,改写 Via、Contact、SDP 里的地址和端口,并在防火墙上开临时 pinhole。但这台路由器的 ALG 实现不完整,改写了 SDP 却没同步改 Contact,或者 pinhole 只开了一个方向,结果比不改还糟。
第四步解释 30 秒断线。SIP 会话定时器(session timer)会在通话中途发 re-INVITE 刷新会话;re-INVITE 的 Contact 地址被 ALG 改错,对端回不到正确地址,刷新失败,双方按超时策略挂断。30 秒正好是不少运营商中继的会话刷新周期。
第五步做对照实验。先在路由器上关闭 SIP ALG,再为 PBX 配置静态端口映射:UDP 5060 映射到 192.168.10.20:5060,RTP 端口段 10000–20000 整段映射到 PBX;同时在 PBX 的 NAT 设置里填好公网 IP、本地网段和 RTP 范围。改完后外呼测试:双向语音正常,连续通话 30 分钟不断,抓包里 SDP 保持 PBX 自己的地址,由 NAT 做一对一转换,不再被中间设备改写。
第六步验证不同运营商。公司有两条 SIP 中继,一条对 NAT 友好,一条要求走 outbound proxy;在 PBX 上给后者配置代理后,两条中继都稳定。说明 ALG 不是必需的,正确的方式是让 PBX/SBC 自己处理 NAT 穿越。
解决方案
- 关闭 SIP ALG:在路由器上找到 SIP ALG / SIP Helper 选项并关闭。大多数场景下,ALG 带来的问题多于它解决的问题;NAT 穿越应该由 PBX 或 SBC 负责。
- 做静态端口映射:把 SIP 信令端口(UDP/TCP 5060,若用 TLS 则 5061)和 RTP 端口段(例如 UDP 10000–20000)固定映射到 PBX;不要依赖 ALG 动态开 pinhole。
- 配置 PBX 的 NAT 参数:在 Asterisk/FreePBX 里填写公网 IP、本地网段、RTP 起止端口,启用
force_rport、rewrite_contact等选项,让 PBX 在信令里使用正确的可达地址。 - 按运营商要求走代理:部分 SIP 中继要求 outbound proxy 或注册到指定 SBC,按运营商文档配置;不要在路由器上靠 ALG 硬穿。
- 保持会话与端口一致:RTP 端口段在 PBX、防火墙、路由器三处保持一致;开启 keep-alive 或 OPTIONS 探测,避免 NAT 绑定提前老化。
- 验证:外呼、被叫、转接、会议各测一遍,确认双向语音;连续通话 30 分钟以上不断;抓包确认 SDP 未被中间设备改写,RTP 双向收发。
根因分析
直接根因是 路由器的 SIP ALG 对 SIP 报文做了不完整的改写:SDP 中的媒体地址/端口与 Contact 头不一致,对端把 RTP 发到了错误地址,造成单向语音;会话定时器的 re-INVITE 也因 Contact 错误失败,通话在 30 秒左右被挂断。
更深一层,是把 NAT 穿越的责任交给了路由器的 ALG。SIP 是一个把地址写在报文里的协议,NAT 会破坏这些地址;正确的做法是由最了解自身拓扑的 PBX/SBC 来做地址转换和代理,而不是让一个通用路由器去猜。ALG 的实现质量参差不齐,遇到加密信令、非标准端口、多网卡场景更容易出错。
还有一层是“默认开启”的隐形配置。路由器出厂默认打开 SIP ALG,安装时没人注意;旧办公室的路由器恰好 ALG 实现正常或默认关闭,搬家换设备后问题才暴露。VoIP 上线验收只测了“能打通”,没有测“双向语音”和“长通话”,所以问题在上线后才被用户发现。
预防措施
- 路由器基线配置默认关闭 SIP ALG/SIP Helper,VoIP 场景明确记录;更换出口设备时按基线复核。
- VoIP 上线验收包含四项:双向语音、长通话(30 分钟以上)、转接/会议、抓包确认 SDP 未被改写。
- SIP 与 RTP 端口映射写成文档,PBX、防火墙、路由器三处保持一致;RTP 段变更时同步更新。
- 优先使用运营商 SBC/outbound proxy 或企业 SBC 处理 NAT 穿越,不依赖路由器 ALG。
- 监控通话质量与断线率,按中继和方向聚合;单向语音、30 秒断线这类特征出现时先查 ALG 和端口映射。
总结
SIP ALG 看起来是在“帮 NAT 的忙”,实际上是在一个它并不了解完整拓扑的位置上改写信令;改对了是运气,改错了就是单向语音和 30 秒断线。VoIP 的 NAT 穿越应该由 PBX/SBC 自己做,路由器只需要关掉 ALG、把端口映射做对。把“能打通”升级成“双向、能久聊、抓包干净”,电话系统才算真正上线。