Kamailio 是一款非常强大的 SIP 代理服务器,Kamailio 一般转发 SIP 信令,不主动产生和发送 SIP 信令。但有时您可能希望 Kamailio 向 IPPBX 注册、主动发 SIP 消息,等等,也就是让 Kamailio 起到客户端的作用,这就需要用到 UAC 模块。
UAC 模块包含的内容很多,但官方手册给的例子比较少,想要熟练掌握就需要做很多练习。本文分几个方面进行说明,希望起到抛砖引玉的效果。

作者:韩小仿
来源:FreeSWITCH中文社区
原文:https://mp.weixin.qq.com/s/xfra6LUedHYngC5s6gaFxw本文内提到的《Kamailio实战》,您可关注小樱桃科技公众号,在菜单栏选择小樱桃商城跳转购买。
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(ruri) = $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 的压力测试,稳定且效率高,值得您一试。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。