团队博客
Kamailio rr 的秘密
韩小仿 2024-04
UAC Kamailio PROXY UAS ---- INVITE ------> record_route() ----- INVITE ----> add_rr_param(";foo=true") --- reINVITE -----> loose_route() ---- reINVITE ---> check_route_param(";foo=true") <-- reINVITE ------ loose_route() <--- reINVITE ---- check_route_param(";foo=true") <------ BYE ------- loose_route() <----- BYE ------- check_route_param(";foo=true")
这意思就是说,UAC 和 UAS 之间所有的 SIP 消息都要经过 Kamailio,包括 Request 消息和 Reply 消息。
rr 模块函数很少,但不容易理解,能参考的路由脚本不多。笔者根据自己的经验整理此文,希望对读者有所裨益。
同网段路由
UAC、Kamailio 以及 UAS 都在同一个网段,这种情况下的路由非常简单,调用 record_route() 之后,Kamailio 自动加上自己的 IP 地址和端口。
listen=udp:KAM_IP:KAM_PORT
双网卡路由
最典型的属于一个网卡在内,另外一个网卡对公网。这种情况的路由也比较简单,调用 record_route() 之后,Kamailio 自动加上双 rr 头。
listen=udp:eth0:5060 listen=udp:eth1:5060
但需要注意下面几个要点:
modparam("rr", "enable_double_rr", 1),模块参数要使能双 rrmhomed = 1,定义这个全局参数- 定义清楚主机上的路由,比如只能通过
eth0去到内网,只能通过eth1去到公网。如果去到公网即可以通过eth0,又可以通过eth1,那可能就麻烦了
单网卡
阿里云的网络就是典型的1:1 NAT,只有一个网卡。
listen=udp:内网地址:SIP端口 advertise 公网地址:SIP端口
VOS-----Kamailio-----Fs
比较典型的是:VOS 看到 Kamailio 的公网地址,Fs 看到 Kamailio 的内网地址。
这里一般有二种处理办法,一种是单 rr,另外一种是双 rr。
我们一个一个讲。
单 rr 显然只能给 Kamailio 的公网地址,但 Fs 虽然通常跟 Kamailio 同在一个内网网段,这就需要 Fs 能访问 Kamaialio 的外网地址。
那有没有变通方法,让 Fs 只访问 kamailio 的内网地址呢?
当然有!
就是做一条iptables nat规则,似外实内。
下面是一个例子:
iptables -t nat -A OUTPUT -d 113.11.22.33 -p udp --dport 5060 -j DNAT --to-destination 10.167.82.4:5060
另外一个办法是使能双 rr,可以这样定义 Kamailio 网络拓扑:
listen=udp:KAM_IP4_PRIVATE_ADDR:KAM_SIP_LAN_PORT listen=udp:KAM_IP4_PRIVATE_ADDR:KAM_SIP_WAN_PORT advertise MY_IP4_PUBLIC_ADDR:KAM_SIP_WAN_PORT
也就是定义二个不同的 SIP 端口
路由里面调用record_route, Kamaiilio 能理解上面的网络拓扑,自动加上双 rr
要避免这样定义:
listen=udp:KAM_IP4_PRIVATE_ADDR:KAM_SIP_LAN_PORT listen=udp:KAM_IP4_PRIVATE_ADDR:KAM_SIP_WAN_PORT advertise=MY_IP4_PUBLIC_ADDR
一般看到这里就可以收工了。
下面讲特别场景下的 rr。
MS Teams
Kamailio 可以做为微软 MS Teams 的 Direct Routing。
但微软 MS Teams 对 rr 头有特别的要求,只能用域名,不能用 IP 地址,那怎么办呢?
可参考下面的代码:
route[RECORD_ROUTE] { if (isflagset(FLT_SRC_MS_TEAMS)) { record_route_preset("KAM_IP4_ADDR:KAM_SIP_PORT;transport=tcp", "KAM_DOMAIN:KAM_SIPS_PORT;transport=tls"); add_rr_param(";r2=on"); } else if isflagset(FLT_DST_MS_TEAMS) { record_route_preset("KAM_DOMAIN:KAM_SIPS_PORT;transport=tls", "KAM_IP4_ADDR:KAM_SIP_PORT;transport=tcp"); add_rr_param(";r2=on"); } else { record_route(); } return; }
特殊网络拓扑下的 rr
下面讲到的更加特别。
Kamailio 单网卡。
IMS -------------- Kamailio ------------ Fs 10.2.3.4 listen 192.168.1.100 172.16.1.99 外网地址 10.2.3.5 外网地址 172.16.1.100
对于 IMS 来说,只能用 10.2.3.5 访问 Kamailio
对于 Fs 来说, 只能用 172.16.1.100 访问 Kamailio
Kamailio 配置如下:
listen=udp:192.168.1.100:5060 alias=10.2.3.5:5060 alias=172.16.1.100:5060
路由脚本需要特别处理:
如果 INVITE 来自 IMS ,设置分支标志 FLB_SRC_IMS_DST_FS,不能调用 record_route(Kamailio 理解不了这种网络拓扑),而是 route(rr)。
如果 INVITE 来自 Fs, 设置分支标志 FLB_SRC_FS_DST_IMS,同理,需要 route(rr)。
下面是 route[rr] 的具体实现:
route[rr] { if (isflagset(FLT_SRC_FS_DST_IMS)) { record_route_preset("10.2.3.5:5060", "172.16.1.100:5060"); add_rr_param(";r2=on"); else if (isflagset(FLT_SRC_IMS_DST_FS)){ record_route_preset("172.16.1.100:5060", "10.2.3.5:5060"); add_rr_param(";r2=on"); } else { record_route(); } return; }
rtpengine 要怎么处理呢?
可以这样定义 rtpengine 的 interface:
interface=IMS/192.168.1.100!10.2.3.5;FS/192.168.1.100!172.16.1.100
对应的路由为:
route[NATMANAGE] { ... $xavp(r=>$T_branch_idx) = "replace-origin replace-session-connection"; if (is_request() && !has_totag()) { if (isflagset(FLT_SRC_FS_DST_IMS)) { $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + ”direction=FS direction=IMS" } else if (isflagset(FLT_SRC_IMS_DST_FS)){ $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + ”direction=IMS direction=FS" } } rtpengine_manage($xavp(r=>$T_branch_idx)); ... }
参考资料:
https://kb.smartvox.co.uk/opensips/contact-and-record-route-headers-explained/