团队博客
Kamailio UAC 模块简述
韩小仿 2024-04
Kamailio 是一款非常强大的 SIP 代理服务器,Kamailio 一般转发 SIP 信令,不主动产生和发送 SIP 信令。但有时您可能希望 Kamailio 向 IPPBX 注册、主动发 SIP 消息,等等,也就是让 Kamailio 起到客户端的作用,这就需要用到 UAC 模块。
UAC 模块包含的内容很多,但官方手册给的例子比较少,想要熟练掌握就需要做很多练习。本文分几个方面进行说明,希望起到抛砖引玉的效果。
uac_replace、uac_restore
先看下面的流程:
UAC Kamailio UAS <-----> <-----> 原始主被叫保持不变 新的主叫和被叫
例如 1001 呼叫 1002,通过下面的路由脚本就可以把主叫号码修改成 alice,被叫号码修改成 bob:
uac_replace_from('"alice"', "sip:" + "alice" + "@" + $fd); uac_replace_to('"bob"', "sip:" + "bob" + "@" + $td);
但问题是如何处理后续的 SIP 请求(比如ACK、Re-Invite、BYE等),UAC 跟 Kamailio 之间始终保持 1001 是主叫,1002 是被叫,Kamailio 跟 UAS 之间始终都是 alice 是主叫,bob 是被叫。
这涉及到下面三个问题:
- 需要保存新的主被叫号码
- 主被叫号码保存到哪里,rr 头 或者对话变量
- 恢复主被叫号码的方式有自动和手工
先看下面的模块参数配置:
modparam("rr", "append_fromtag", 1) # 必须为 1 modparam("uac", "restore_mode", "auto") # 自动恢复方式 modparam("uac", "restore_dlg", 0) # 不从对话变量中恢复 modparam("uac", "rr_from_store_param", "vsf") # 利用 rr 头的 vsf 参数来进行 from 的保存和恢复 modparam("uac", "rr_to_store_param", "vst") # 利用 rr 头的 vst 参数来进行 to 的保存和恢复 modparam("uac", "restore_passwd", "my_secret_passwd") # 密码保存到 rr 头的 my_secret_passwd, 密码加密后保存
用 sngrep 跟踪呼叫,下面是 Kamailio 收到的 INVITE 包
INVITE sip:1002@192.168.0.103 SIP/2.0 Via: SIP/2.0/UDP 192.168.0.100:22040;branch=z9hG4bK-d87543-8a6dd8262a483446-1--d87543-;rport Max-Forwards: 70 Contact: <sip:1001@192.168.0.100:22040> To: "1002"<sip:1002@192.168.0.103> From: <sip:1001@192.168.0.103>;tag=4955d54f ...
经过 Kamailio 路由处理之后,发出来下面这个包:
INVITE sip:1002@192.168.0.100:5066;ob SIP/2.0 Record-Route: <sip:192.168.0.103;lr;ftag=4955d54f;vsf=bXlfczU/KzdRLnhqb2xwantnQWxkYEE-;vst=bXlfczY8IBcFV3t9bHR5cnNnQHJmUA--> Via: SIP/2.0/UDP 192.168.0.103;branch=z9hG4bKdeb8.9ad64c208ecf50f14f8fff328827fc07.0 Via: SIP/2.0/UDP 192.168.0.100:22040;received=192.168.0.100;branch=z9hG4bK-d87543-8a6dd8262a483446-1--d87543-;rport=22040 Max-Forwards: 69 Contact: <sip:1001@192.168.0.100:22040> To: "bob"<sip:bob@192.168.0.103> From: "alice" <sip:alice@192.168.0.103>;tag=4955d54f ...
很明显,rr 头多了 vsf 和 vst 参数。
下面尝试把主被叫号码保存到对话变量,模块参数配置如下:
modparam("rr", "append_fromtag", 1) # 必须为 1 modparam("dialog", "db_mode", 0) # dialog 的对话变量不保存到数据库 modparam("uac", "restore_mode", "auto") # 自动恢复方式 modparam("uac", "restore_dlg", 1) # 从对话变量中恢复
现在发起一个呼叫,被叫应答之后,运行 kamcmd dlg.list,输出为:
{ h_entry: 4021 h_id: 10506 ref: 2 call-id: MGUwYjRiNjEzMTA5MGUxOGZhMDljYWZjNDdkOTQyYTM. from_uri: sip:1001@192.168.0.103 to_uri: sip:1002@192.168.0.103 state: 4 start_ts: 1726108372 init_ts: 1726108372 end_ts: 0 duration: 2 timeout: 1726151572 lifetime: 43200 dflags: 643 sflags: 0 iflags: 0 caller: { tag: 024f2837 contact: sip:1001@192.168.0.100:25946 cseq: 1 route_set: socket: udp:192.168.0.103:5060 } callee: { tag: 42bca8c3647e483d926a125b9ddc671b contact: sip:1002@192.168.0.100:5066;ob cseq: 0 route_set: socket: udp:192.168.0.103:5060 } profiles: { } variables: { { _uac_tdpnew: "bob" } { _uac_tdp: "1002" } { _uac_tonew: sip:bob@192.168.0.103 } { _uac_to: sip:1002@192.168.0.103 } { _uac_fdpnew: "alice" } { _uac_fdp: } { _uac_funew: sip:alice@192.168.0.103 } { _uac_fu: sip:1001@192.168.0.103 } } }
可以看到,原始的主被叫号码和修改后的主被叫号码都记录到了对话变量,方便以后做恢复处理。
顺便提下,如果把 dialog 模块的 db_mode 的值从 0 改成 2,那么 kamailio 重启时会自动从数据库里面读入对话变量的值。
uac_reg_send
一般用 uac_reg_send 发送 OPTIONS 或者 MESSAGE,下面是一段路由代码(任意路由都可以执行):
loadmodule "uas.so" loadmodule "jansson.so" ... route[UAC_MESSAGE] { $var(aor) = "1001@192.168.0.103"; # 发送 MESSAGE 到 1001 这个注册用户 $var(text) = "Hello, World!\r\n"; # 待发送的文本内容 $var(from) = "sip:admin@192.168.0.103"; # rpc 请求报文 $var(req) = $_s({"jsonrpc":"2.0", "method":"ul.lookup","params":["location","$var(aor)"], "id":1}); jsonrpc_exec("$var(req)"); if ($jsonrpl(code) != 200) return; # 暂时只处理一个 contact jansson_get("result.Contacts[0].Contact.Address", "$jsonrpl(body)", "$var(address)"); if (jansson_get("result.Contacts[0].Contact.Received", "$jsonrpl(body)", "$var(received)")) { $var(outbound_proxy) = 0; } else { $var(outbound_proxy) = 1; } # uac 伪变量可参考这里: https://www.kamailio.org/wikidocs/cookbooks/devel/pseudovariables/#uac_reqkey $uac_req(method) = "MESSAGE"; if ($var(outbound_proxy)) { $uac_req(ouri) = $var(received); } $uac_req(ruri) = $var(address); $uac_req(furi) = $var(from); $uac_req(turi) = $var(address); $uac_req(hdrs) = "Subject: Emergency Alert\r\n"; $uac_req(hdrs) = $uac_req(hdrs) + "Content-Type: text/plain\r\n"; $uac_req(body) = $var(text); $uac_req(evroute) = 1; # 触发 event_route[uac:reply] uac_req_send(); # 发送 } event_route[uac:reply] { xinfo("===uac reply received, callid = $uac_req(callid), tu = $uac_req(turi), code = $uac_req(evcode)\n"); } event_route [tm:local-request] { if ($rm == "MESSAGE") { xinfo("$ci|Routing locally generated $rm to $ru, callid = $ci\n"); t_set_fr(1000, 10000); } }
早期版本有个 BUG, $uac_req(callid) 最多只能到 128 字节,超过了就会崩溃,但早已修复。
uac_reg、uac_auth
uac_reg 是 Kamailio 作为 SIP 客户端 向 IPPBX(例如 FreeSWITCH)或者 SIP 代理服务器(例如 OpenSIPS)注册。
uac_auth 是 Kamailio 自己完成 SIP 认证(而不是转发 UAC 认证请求)。
《Kamailio实战》的第八章的第十一节对此已有很详细的介绍,这里仅补充几点:
- l_uuid 是 uacreg 表的主键,不能包含逗号(“,”)、艾特(“@”) 等符号,但可以有下划线。逗号在 SIP 协议里面有专门的含义,不能用。艾特是 UAC 模块不让用
- uacreg 表的 realm 字段可以是默认值(默认值是''),这样就会采用收到的 SIP 包里面的 realm 字段(一般是 401/407),减少发生冲突的可能性
- uacreg 表的 contact_addr 字段可以是默认值(默认值是''),如果有特殊考虑,也可以进行配置,以便覆盖 UAC 模块的 reg_contact_addr 参数
- UAC 模块发出的 REGISTER 请求,其 contact 头一般是
l_uuid@contact_addr,contact_addr 支持;transport=tcp
本人专门做过 uac_reg 的压力测试,稳定且效率高,值得您一试。