基于Kamailio和RTPProxy实现FreeSWITCH SIP和RTP数据包负载均衡
基于Kamailio和RTPProxy实现FreeSWITCH SIP和RTP数据包负载均衡
一、环境
- debian bullseye
- Kamailio 5.7.2
- RTPProxy 3.0-rc.1.2dbc63b9
- FreeSWITCH· 1.10.10
二、安装Kamailio和RTPProxy
直接安装Kamailio:
sudo apt-get update sudo apt-get install kamailio kamailio-mysql-modules
源码安装RTPProxy:
$ git clone -b master https://github.com/sippy/rtpproxy.git $ git -C rtpproxy submodule update --init --recursive $ cd rtpproxy $ ./configure $ make && make install
启动RTPProxy服务
#服务启动 # -l 对外的地址 # -s 后面的参数就是kamailio配置的rtpproxy_socket rtpproxy -l 192.168.1.2 -s udp:192.168.1.2:7722
RTPProxy 常用参数解析:
./rtpproxy -A 110.172.192.68 -l 0.0.0.0 -s udp:localhost:7890 -n tcp:localhost:7890 -F -d DBUG -m 50002 -M 50020 -d
参数解释如下:
-A 本机外网地址
-l 本机内网侦听地址
-s 本机侦听kamailio通知端口
-n 超时通知接收端口
-F 不检查是否为超级用户模式
-d 调试消息输出级别
-m rtp最小端口
-M rtp最大端口
-d
log_level为日志级别,值如下
DBUG INFO WARN ERR CRIT
log_facility为日志输出到哪,利用的系统日志
三、Kamailio中的RTPProxy模块
3.1 描述
- rtpproxy模块可以支持多个rtpproxy,用于平衡/分发、控制/选择目的。
- 允许定义几组rtpproxy。负载平衡将在一个集上执行,管理员可以选择应该使用的集合。集合是通过它的id来选择的——id是由集合定义的。
- rtpproxy模块根据集合中每个rtpproxy的权重自动完成集合内的平衡。
- 集合的选择是在使用unforce_rtp_proxy(), rtpproxy_offer()或rtpproxy_answer()函数之前从脚本中完成。
- 特别说明:如果有多个集合,则在调用rtpproxy_offer()/rtpproxy_answer()和unforce_rtpproxy()等方法时要使用相同的集合。
3.2 模块参数
参数名 | 类型 | 说明和示例 |
rtpproxy_sock | string |
可以是单个socket udp:localhost:12221 也可以是一组sockert,默认集合是0 udp:localhost:12221 udp:localhost:12222 也可以指定集合id,应 “id==”指定 1 == udp:localhost:12221 udp:localhost:12222 |
rtpproxy_disable_tout | integer |
默认60 当RTPProxy 无法访问并标记为禁用 |
rtpproxy_tout | integer |
默认1 等待rtpproxy回复的超时时间 |
rtpproxy_retr | integer |
默认5 重试次数 |
nortpproxy_str | string |
默认值为“a=nortpproxy:yes\r\n” 设置到sdp属性里,标记已经被更改 |
timeout_socket | string |
默认控 发给rtpproxy,rtpproxy用来做重传 |
ice_candidate_priority_avp | string | |
extra_id_pv | string | |
db_url | string |
数据库url(当设置了此参数,rtpproxy_sock参数将失效) 用来加载rtpproxy数据 |
table_name | string | 表名 |
rtp_inst_pvar | string | 设置伪变量,如果设置了该参数,选中的rtpproxy的URL将存储在给定的变量中 |
3.3 模块方法
方法名 | 参数 | 说明 |
set_rtp_proxy_set | setid | 设置要使用的rtpproxy集合id |
unforce_rtp_proxy | [flag] | 断开 |
rtpproxy_destroy | 同 unforce_rtp_proxy | |
start_recording | 发送标记到RTPProxy,用来记录RTP Stream | |
rtpproxy_offer | [flag [,ipaddr]] |
重写SDP,让rtp流通过rtpproxy转发 在Invite时触发 |
rtpproxy_answer | 参数同rtpproxy_offer |
重写SDP,让rtp流通过rtpproxy转发 在200OK响应 |
rtpproxy_manage | 参数同rtpproxy_offer | 会自动选择需要执行的方法rtpproxy_offer、rtpproxy_answer、unforce_rtpproxy |
3.4 流程逻辑
- kamailio收到invite请求
- 调用rtpproxy服务,rtpproxy创建会话,返回接收地址和端口
- 将rtpproxy返回的地址和端口修改到sdp
- 将修改后的invite请求发送到设备
- kamailio收到200 OK响应
- 调用rtpproxy服务,将200 OK携带的发送地址和端口通知过去,返回rtpproxy转发出来的地址和端口
- 将返回的地址和端口修改到200 OK的sdp
- 将修改后的200 OK返回到invite方
3.5 关键方法说明
主要用到的几个方法是rtpproxy_manage、rtpproxy_answer、rtpproxy_offer,它们使用方式基本一致,且rtpproxy_manage会根据sip消息自动决定使用rtpproxy_answer还是rtpproxy_offer。所以主要以rtpproxy_manage为例说明。
rtpproxy_manage([flag [, ipaddr]])
带有2个可选参数
a. flag:只指定flag
b. flag,ipaddr:指定flag和ip地址(指定ip地址后,修改sdp时会强制用指定的ip)
flag参数解释
- 1:在向rtpproxy发送命令时,将第一个Via分支附加到Call-ID
- 2:在向rtpproxy发送命令时,将第二个Via分支附加到Call-ID
- 3:如果是request消息,在向rtpproxy发送命令时,将第一个Via分支附加到Call-ID;如果是reply消息,在向rtpproxy发送命令时,将第二个Via分支附加到Call-ID
- a:表示对端不自持对称RTP,会自动设置r参数
- b:在向rtpproxy发送命令时,将extra_id_pv变量的值附加到Call-ID,必须设置extra_id_pv变量,注意:b不能与1、2、3同时使用
- l:强制“查找”,只有当相应的会话在rtpproxy中已经存在时才重写SDP
- i/e:标志指定SIP消息的方向。i”表示内部网络(LAN),“e”表示外部网络(WAN)。使用时必须指定两个标志来定义传入网络和传出网络,且请求和响应必须一致。比如请求使用“ie”,那么响应必须也是“ie”,可以有以下组合:ie、ei、ii、ee
- x:该标志是使用RTPProxy的“ie”或“ei”标志的快捷方式,以便在“内部网络”的IPv4和“外部 网络”的IPv6之间自动桥接。区别是由SDP中给定的IP完成的,例如,IPv4地址将始终调用“ie”到RTPProxy (IPv4(i)到IPv6(e)),而IPv6Address将始终调用“ei”到RTPProxy (IPv6(e)到IPv4(i))
- f:告诉rtpproxy忽略正在传输中的另一个rtpproxy插入的标记,以指示会话已经通过另一个代理
- r:标记sdp内的地址有效,如果没有这个参数,则默认使用sip消息的源地址作为媒体地址
- c:修改session的c字段的ip
- w:接收消息的UA,必须强制支持对称RTP
- z:修改payload大小
3.6 示例
#加载rtpproxy模块 loadmodule "rtpproxy.so" #设置rtpproxy_sock,也可以通过加载rtpproxy数据库来设置 modparam("rtpproxy", "rtpproxy_sock", "udp:192.168.1.2:7722") request_route { if(is_method("REGISTER")) { ... } else { ... #收到非register请求时,尝试判断触发RTPPROXY if (is_method("INVITE|UPDATE")) { route(RTPROXY); } } route(RELAY); return; } route[RELAY] { if (is_method("INVITE|UPDATE")) { #响应 if(!t_is_set("onreply_route")) { t_on_reply("MANAGE_REPLY"); } } if (is_method("INVITE")) { #失败 if(!t_is_set("failure_route")){ t_on_failure("MANAGE_FAILURE"); } } if (!t_relay()) { sl_reply_error(); } exit; } route[RTPROXY] { #触发rtpproxy,模块内部会根据sip消息内容自动选择rtpproxy_offer、rtpproxy_answer或者执行 # 参数 “r”,会使用sdp里的地址 rtpproxy_manage("r"); return; } # Manage incoming replies in transaction context onreply_route[MANAGE_REPLY] { #成功触发 if(status=~"[12][0-9][0-9]") { route(RTPROXY); } return; } # Manage failure routing cases failure_route[MANAGE_FAILURE] { #失败触发 route(RTPROXY); return; }