团队博客
Kamailio 案例分享
韩小仿 2025-05
笔者在使用 Kamailio 和支持客户的过程中,碰到过一些问题,也攒了一些经验教训,现分享如下
Kamailio 部署在阿里云,没有配置 advertise 地址,导致
主要的路由脚本如下:
request_route { ... if (!mf_process_maxfwd_header("10")) { sl_send_reply("483", "Too Many Hops"); exit; } route(SIPOUT); ### requests for my local domains # handle registrations route(REGISTRAR); ... } route[SIPOUT] { if (uri==myself) return; route(RELAY); exit; }
终端发起 REGISTER 请求,$rd(请求域) 是 阿里云的公网地址,当执行到 SIPOUT 子路由时,因为没有配置 advertise 地址,导致 uri 不是 myself, 于是执行 t_relay(),sip 头 Forward 的值会自动递减,再转发到阿里云的公网地址。
当转发次数达到一定程度时 Kamailio 给终端回应 483 Too Many Hops。
加上这一行配置之后就正常了:
listen=udp:172.16.1.100:5060 advertise 113.113.113.113:5060 # 本机地址和外网地址
mf_process_maxfwd_header("10") 的逻辑如下:
- 找到 Forward 头,并且成功递减(非0),返回 1
- 找到 Forward 头,但值为 0,无法递减,返回 -1(失败)
- 没有 Forward 头,Kamailio 自动添加 SIP 头
Forward:10\r\n,返回 2
路由少了 exit, 导致
主要的路由脚本如下:
request_route { ... # handle registrations route(REGISTRAR); if ($rU==$null) { # request with no Username in RURI sl_send_reply("484", "Address Incomplete"); exit; } ... } route[REGISTRAR] { if (!is_method("REGISTER")) return; if(isflagset(FLT_NATS)) { setbflag(FLB_NATB); #!ifdef WITH_NATSIPPING # do SIP NAT pinging setbflag(FLB_NATSIPPING); #!endif } if (!save("location")) { sl_reply_error(); } # exit; }
终端发起 REGISTER 请求,REGISTRAR 子路由处理完注册请求之后,应该调用 exit,结束路由的执行;但因为此处路由脚本忘记写 exit,导致主路由继续执行,下一步检查 $rU(请求用户) 是否有空,但我们知道注册请求行只有 $rd(请求域),没有 $rU(请求用户);于是路由继续往下执行,返回 484 Address Incomplete。
TCP 连接数的问题
现象是终端用 TCP 协议注册到 Kamailio,当终端数量超过二千多一点的时候,发现有部分终端注册失败。
查核心文档,tcp_max_connections 默认是 2048,配置为 4096 之后,就正常了。
配错了 alias 导致 ACK 转发失败
Kamailio 和 FreeSWITCH 都部署在同一台服务器上,比如:
FreeSWITCH SIP 端口为 6666, Kamailio SIP 端口为 5060
Kamailio 这样配置:
listen=udp:192.168.1.100:5060 alias=192.168.1.100 # 不应该这样配置,应该配置域名
注册用户透过 Kamailio 呼叫 FreeSWITCH,后者应答,注册用户回 ACK,ACK 包如下:
ACK sip:1001@192.168.1.100:6666 SIP/2.0 Route: <sip:192.168.1.100:5060;lr> ...
ACK 请求行的地址显然是 FreeSWITCH 的地址,ACK 消息应该转发到 FreeSWITCH。
当 Kamailio 收到这个消息时执行的路由是:
route[WITHINDLG] { if (!has_totag()) return; if (loose_route()) { route(DLGURI); ... } ...
执行 loose_route() 时,Kamailio 先做严格路由的检查,如果严格路由失败才会继续检查松散路由。
严格路由比较请求行的地址是不是 myself,如果是,那么就转发给该地址(Kamailio自己),
而 alias=192.168.1.100 这样的配置会导致不比较协议,不比较端口,只比较地址。
这就导致 ACK 消息转发给 Kamailio 自己(sip:192.168.1.100),而不是 FreeSWITCH(sip:192.168.1.100:6666)。
解决此问题有二个办法:
- 删除这一行配置
alias=192.168.1.100 - 把 loose_route() 更换成 loose_route_mode(1),后者只做检查松散路由,不再检查严格路由
这个问题跟什么是 myself 有关,可以运行 kamcmd core.aliases_list,下面是一个输出的例子:
{ myself_callbacks: no aliases: { alias: { proto: * address: test.com.org port: * } alias: { proto: * address: 113.113.113.113 port: 5060 } alias: { proto: * address: 192.168.1.100 port: 5060 } } }
判断一个地址是不是 myself,需要检查这三个 alias,只要通过了其中之一,那就是 myself,如果这三个都不是,那么就不是 myself。
第一个 alias 只比较地址, 剩下的二个需要同时比较地址和端口。
跟
示意图如下:
UA------Kamailio------FreeSWITCH (NAT) (阿里云主机) (阿里云另外台主机)
问题的流程是:
- 注册用户(UA)呼叫 Kamailio
- Kamailio
ds_select()到 FreeSWITCH,基于某些方面的考虑,SIP 传输配置成了 TCP - FreeSWITCH 应答,Contact 头是 FreeSWITCH 的公网地址(也可以配置为内网地址,但因为各种原因,不能改动这个配置)。Kamailio 转发此应答消息
- 注册用户收到应答消息之后发 ACK 消息
- Kamailio 收到 ACK 之后,要转发给 FreeSWITCH 的公网地址,但 ACK 是一个新的 SIP 事务,这需要 Kamailio 新建一个从 Kamailio 到 FreeSWITCH 的 TCP 连接,才能转发 ACK
- 路由脚本需要在对话内路由(
route[WITHINDLG]) 调用set_forward_no_connect()。如果没有调用函数,那么就转发不了 ACK 消息
下面这段日志可供参考:
DEBUG: LUA {ACK}: <core> [core/tcp_main.c:2047]: tcp_send(): no connect set and no active connection ERROR: LUA {ACK}: <core> [core/forward.h:261]: msg_send_buffer(): tcp_send failed ERROR: LUA {ACK}: sl [sl_funcs.c:418]: sl_reply_error(): stateless error reply used: I'm terribly sorry, server error occurred (1/SL)
双网卡碰到的问题
主机有两网卡,分别是:
- eth0, ip 为 172.16.1.100,对接运营商的 sip 地址为 172.16.1.200
- eth1, ip 为 192.168.1.100,对接 FreeSWITCH 的 sip 地址为 192.168.1.200
网卡路由是正确的,去运营商要经过 eth0, 去 FreeSWITCH 要经过 eth1,不能乱
编辑 /etc/kamailio/dispatcher.list, 内容为:
#setid destination flags priority attrs description 1 sip:172.16.1.200 8 0 ; carrier 2 sip:192.168.1.200 8 0 ; fs
编辑 /etc/kamailio/kamailio.cfg, 与此相关的内容为:
listen=udp:eth0:5060 listen=udp:eth1:5060 loadmodule "dispatcher.so" modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list") modparam("dispatcher", "flags", 2) modparam("dispatcher", "ds_probing_mode", 3) modparam("dispatcher", "ds_probing_threshold", 3) modparam("dispatcher", "ds_ping_interval", 30) modparam("dispatcher", "ds_ping_reply_codes", "class=2;code=403;code=404;code=488;class=3") modparam("dispatcher", "xavp_dst", "_dsdst_") modparam("dispatcher", "xavp_ctx", "_dsctx_")
启动 kamailio 之后用 sngrep 跟踪,发现去到运营商和去到 FreeSWITCH 都走 eth0,这与预期不符
解决的办法有二个,第一个配置全局参数 mhomed=1,其他不动;另外一个办法是修改 dispatcher.list 的属性(attrs)
#setid destination flags priority attrs description 1 sip:172.16.1.200 8 0 socket=udp:172.16.1.100:5060 carrier 2 sip:192.168.1.200 8 0 socket=udp:192.168.1.100:5060 fs
就是明确告诉 kamailio 从哪个 socket 出发