前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Kamailio UAC 模块简述

Kamailio UAC 模块简述

作者头像
杜金房
发布于 2025-03-13 13:37:03
发布于 2025-03-13 13:37:03
11800
代码可运行
举报
运行总次数:0
代码可运行

本文内提到的《Kamailio实战》,您可关注小樱桃科技公众号,在菜单栏选择小樱桃商城跳转购买。

Kamailio 是一款非常强大的 SIP 代理服务器,Kamailio 一般转发 SIP 信令,不主动产生和发送 SIP 信令。但有时您可能希望 Kamailio 向 IPPBX 注册、主动发 SIP 消息,等等,也就是让 Kamailio 起到客户端的作用,这就需要用到 UAC 模块。

UAC 模块包含的内容很多,但官方手册给的例子比较少,想要熟练掌握就需要做很多练习。本文分几个方面进行说明,希望起到抛砖引玉的效果

uac_replace、uac_restore

先看下面的流程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
UAC                   Kamailio                 UAS
     <----->                      <----->
   原始主被叫保持不变             新的主叫和被叫

例如 1001 呼叫 1002,通过下面的路由脚本就可以把主叫号码修改成 alice,被叫号码修改成 bob:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 头 或者对话变量
  • 恢复主被叫号码的方式有自动和手工

先看下面的模块参数配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 包:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 路由处理之后,发出来下面这个包:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 参数。

下面尝试把主被叫号码保存到对话变量,模块参数配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,输出为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  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,下面是一段路由代码(任意路由都可以执行):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
loadmodule "uas.so"
loadmodule "jansson.so"
...

route[UAC_MESSAGE] {
  $var(aor) = "1001@192.168.0.103";            # 发送 MESSAGE1001 这个注册用户
  $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 的压力测试,稳定且效率高,值得您一试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeSWITCH中文社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档