作者:韩小仿
来源:FreeSWITCH中文社区
链接:https://mp.weixin.qq.com/s/Ki2JhX6cWYRkwLdv-5ptMA
在SIP协议部署过程中,NAT(网络地址转换)问题是影响通信稳定性的核心痛点之一。Kamailio与FreeSWITCH作为主流的SIP相关组件,虽在NAT处理的实现方式上存在差异,但核心目标一致——解决终端内网地址与公网通信的适配问题,实现异曲同工之效。本文将从NAT检查、注册请求Contact重写、呼叫请求(及应答)Contact重写三个核心场景,对比两者的实现逻辑与操作方式。

NAT检查
NAT检查是处理NAT问题的前提,需先识别出处于NAT环境下的终端,再针对性执行后续适配操作,Kamailio与FreeSWITCH均提供了简洁高效的检查方案。
Kamailio NAT 检查
Kamailio通过内置的nat_uac_test函数实现NAT检查,常用配置为nat_uac_test(18),该参数由2和16两个子参数组合而成,具体含义如下:
- 参数2:将Via头中的地址与信令实际的网络地址进行对比;若Via头中未包含端口信息,则默认使用SIP标准端口5060。
- 参数16:检测信令的源端口与Via头中携带的端口是否不一致;若Via头未包含端口,同样默认使用5060端口作为对比基准。
FreeSWITCH NAT 检查
FreeSWITCH可通过在SIP Profile中配置如下参数,实现对NAT环境的自动检测:
<param name="aggressive-nat-detection" value="true"/>
重写(rewrite)注册请求的Contact
在终端注册场景中,若不重写Contact头,会导致服务器后续向终端发送信令时,误将终端的内网地址作为目标地址,从而引发通信失败。以下结合实际部署案例,说明Kamailio与FreeSWITCH的具体实现方式。
问题场景说明
假设FreeSWITCH部署在阿里云公网服务器(公网IP:11.22.33.44),当内网终端(内网IP:192.168.1.100)向服务器发送注册请求时,REGISTER消息内容如下:
REGISTER sip:11.22.33.44 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100;branch=z9hG4bK-d87543-16520a753361f75c-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:1001@192.168.1.100;rinstance=08b45beb064c2934>
To: <sip:1001@11.22.33.44>
From: <sip:1001@11.22.33.44>;tag=0141d40f
Call-ID: ZWIxNDhmNTMxMWQ2ZjYxNDVjODAyNjcyZjc1M2YyNzc.
CSeq: 1 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
User-Agent: eyeBeam release 1011d stamp 40820
Content-Length: 0
若不进行Contact重写,当FreeSWITCH需要呼叫该终端(分机号1001)时,会将INVITE消息发送至Contact头中携带的192.168.1.100(内网地址),导致信令无法到达终端,呼叫失败。
FreeSWITCH Contact重写(注册场景)
FreeSWITCH有两种常用方式实现注册请求的Contact重写,适配不同部署需求:
- 方式一:启用前文提到的aggressive-nat-detection配置。启用后,FreeSWITCH会自动忽略Contact头中的内网地址,将注册消息到达服务器时的公网地址(即终端的公网出口地址)作为终端的实际通信地址。
- 方式二:通过directory配置添加变量,强制重写Contact头,配置如下:
<variable name="sip-force-contact" value="NDLB-connectile-dysfunction"/>
Kamailio Contact重写(注册场景)
Kamailio通过内置的fix_nated_register函数实现注册请求的Contact重写,需在路由脚本中添加相关逻辑,参考配置如下:
route[NATDETECT] {
if (nat_uac_test("18")) { # 先检测终端是否处于NAT环境
if (is_method("REGISTER")) { # 判断是否为注册请求
fix_nated_register(); # 重写注册请求的Contact头
} else {
if(is_first_hop()) { # 判断是否为第一跳信令
set_contact_alias();
}
}
setflag(FLT_NATS); # 标记该终端为NAT环境,便于后续处理
}
return;
}
重写(rewrite)呼叫请求(和应答)的Contact
除注册场景外,呼叫过程中的INVITE请求(呼叫发起)和应答消息(呼叫确认),若Contact头携带内网地址,会导致后续的BYE、re-INVITE等信令无法正常交互。以下对比Kamailio与FreeSWITCH的解决方案。
问题场景说明
沿用前文部署场景(FreeSWITCH公网IP:11.22.33.44),当内网终端(192.168.1.100)向服务器发起呼叫时,发送的INVITE消息如下:
INVITE sip:9196@11.22.33.44;transport=udp SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100;branch=z9hG4bKe561.23e2b896e23b4a61cdee6de4e4d4c8f6.1
From: "1001" <sip:1001@11.22.33.44>;tag=yBBN5ZeSXSrpN
To: <sip:9196@11.22.33.44;dest=extension>
Call-ID: 72330c7c-0d0b-48ec-bf6f-8453948b15c0
Contact: <sip:1001@192.168.1.100>
若不重写Contact头,当FreeSWITCH需要结束呼叫(发送BYE消息)时,会将消息发送至192.168.1.100(内网地址),导致呼叫无法正常挂断,或后续信令交互异常。
FreeSWITCH Contact重写(呼叫场景)
FreeSWITCH可通过设置通道变量sip_sticky_contact为true,实现呼叫过程中Contact头的动态适配,具体逻辑如下:
- 设置
sip_sticky_contact = true后,FreeSWITCH会自动保存终端呼叫请求到达时的公网地址,并将该地址临时作为Proxy地址。 - 后续执行应答(answer)操作后,若需要发送BYE、re-INVITE等信令,FreeSWITCH会将信令发送至该临时Proxy地址,而非Contact头中的内网地址,确保信令正常交互。
Kamailio Contact重写(呼叫场景)
Kamailio对呼叫请求和应答消息的Contact重写,主要通过路由脚本中的相关函数实现,需分别在请求路由、应答路由和对话内路由中添加逻辑,参考配置如下:
request_route{
...
if (is_method("INVITE")) { # 针对呼叫发起请求(INVITE)
set_contact_alias(); # 重写INVITE消息的Contact头
}
...
}
onreply_route[MANAGE_REPLY] { # 针对呼叫应答消息
...
set_contact_alias(); # 重写应答消息的Contact头
...
}
# 处理对话内的后续请求(如BYE、re-INVITE等)
route[WITHINDLG] {
if (!has_totag()) return;
if (loose_route()) {
if(!isdsturiset()) {
handle_ruri_alias(); # 处理请求URI中的别名参数,辅助信令路由
}
...
}
...
}
总结
Kamailio与FreeSWITCH在NAT处理上的核心差异的在于:Kamailio依赖路由脚本和内置函数,灵活性更高,可根据实际场景自定义NAT检测和Contact重写逻辑,更适用于复杂的信令路由场景;FreeSWITCH则通过配置参数简化操作,上手难度更低,常规场景下无需复杂脚本,即可实现NAT适配,更适用于快速部署、需求相对标准化的通信场景。
两者虽实现路径不同,但均能有效解决SIP通信中的NAT问题,实际部署时可根据自身业务需求、技术架构,选择更贴合的组件及配置方案。
当然SIP NAT 还有 force_rport 等等,欢迎大家讨论交流。
广告时间:
如果您想学习更多Kamailio的内容,可前往小樱桃商城或京东商城购买《Kamailio实战》这本书。
小樱桃商城购买链接:https://jsj.top/f/RJhn5I
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。