团队博客

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 出发

Go语言中的 context 机制详解