1. Kamailio伪变量初探:SIP路由脚本的瑞士军刀
第一次接触Kamailio的伪变量(Pseudo-Variables)时,我正被一个SIP路由逻辑卡住三天——需要在请求头中动态插入带时间戳的Call-ID。当看到$Ts这个神奇符号瞬间解决问题时,那种震撼感至今难忘。伪变量就像藏在Kamailio路由脚本里的魔法道具,用对了能四两拨千斤。
伪变量本质上是一种特殊的变量引用形式,以$符号开头,在Kamailio配置脚本中提供对系统信息、SIP消息元素、运行时数据等的动态访问能力。与普通变量不同,它们:
- 无需声明即用:像
$ru(请求URI)、$fu(From URI)等开箱即用 - 实时动态取值:每次访问时获取最新状态(如
$Ts返回当前时间戳) - 作用域穿透:在路由块、分支处理等场景下保持一致性
举个真实场景的例子:我们需要在REGISTER请求中校验Contact头域的过期时间。传统方式要手动解析SIP消息,而用伪变量只需一行:
if ($ct.fields.expires > 3600) { # 直接访问Contact头的expires字段 xlog("过长的注册过期时间: $ct\n"); }2. 核心伪变量分类与实战用法
2.1 SIP消息解剖类伪变量
这类伪变量直接映射SIP消息的各个组成部分,是路由脚本中最常用的工具集:
$ru(Request URI): 处理INVITE请求时,我曾遇到需要批量替换域名的情况。通过$ru.host = "newdomain.com";直接修改,比正则匹配高效得多。注意修改后会立即影响后续路由逻辑。$hdr(name): 获取任意头域值时,$hdr(Content-Type)比手动解析更可靠。特别提醒:头域名称大小写敏感,$hdr(CALL-ID)和$hdr(Call-ID)结果可能不同。$bav(Body Address Variables): 处理带SDP的INVITE时,$bav(media.port)能快速提取媒体端口。曾有个项目需要限制音频端口范围,用这个变量省去了整个SDP解析库。
表:SIP消息类核心伪变量速查
| 变量 | 等效字段 | 使用示例 | 注意事项 |
|---|---|---|---|
$fu | From头URI | $fu.user获取主叫号码 | 修改会影响后续计费 |
$tu | To头URI | $tu.port=5060强制端口 | 在ACK中可能为空 |
$du | 目标地址 | $du = "sip:1.2.3.4"; | 需先调用lookup() |
$avp(name) | 自定义AVP | $avp(cos) = "gold"; | 跨事务持久化 |
2.2 系统运行时伪变量
这类变量反映Kamailio实例的实时状态信息:
$Ts(Unix时间戳): 生成唯一ID时,配合$Tf(微秒)可以避免冲突:$var(callid) = "call-" + $Ts + "-" + $Tf;。实测在每秒万级请求下仍能保证唯一性。$Rp(本地接收端口): 多网卡环境下,我曾用if ($Rp == 5061) { force_tls(); }实现智能TLS切换。注意与$Lp(监听端口)的区别。$stat(name): 监控场景下特别有用,如xlog("每秒请求量: $stat(rcv_requests)\n");。建议配合$time(%H:%M:%S)打时间戳。
2.3 分支处理类伪变量
处理并行分支(如forking call)时的关键变量:
$br(分支索引): 在失败路由中,if ($br == 0) { send_to_next_gw(); }可以实现主备网关切换。记住分支号从0开始计数。$T_branch_idx: 比$br更精确,能获取当前处理的分支索引。在t_on_branch事件路由中调试时特别有用。
重要提示:修改分支相关变量(如
$du)后,必须调用append_branch()才能生效,这是新手常踩的坑。
3. 伪变量转换与PV模块高阶技巧
3.1 字符串操作转换
pv模块提供的转换函数能让伪变量如虎添翼:
# 提取URI用户部分并转为大写 $var(user) = $(fu.user{uri.escape,upper}); # 时间格式化示例 xlog("当前时间: $(Ts{time.date:%Y-%m-%d %H:%M:%S})\n");实际项目中,我曾用$(hdr(Contact){param.value,expires})快速提取过期参数,比手动字符串处理稳定十倍。转换函数支持链式调用,但要注意性能损耗。
3.2 正则表达式转换
{s.regex}转换的强大之处在于可以就地修改值:
# 将+86号码转换为国际格式 $fu.user = $(fu.user{s.regex,/^\+86([0-9]+)$/\1/});调试技巧:先在xlog中测试正则效果,如xlog("转换结果: $(fu.user{s.regex,...})\n");。曾有个复杂正则导致CPU飙升,后来发现是.*未锚定导致回溯爆炸。
3.3 数据类型转换
处理数字比较时,类型转换能避免很多坑:
# 字符串转整数比较 if (int($ct.fields.expires) > 3600) { sl_send_reply("423", "Interval Too Brief"); }特别提醒:{int}转换遇到非数字会返回0,可能导致逻辑错误。安全做法是先{s.len}检查长度。
4. 调试与性能优化实战
4.1 伪变量调试技巧
在复杂路由中定位问题时,我总结了一套调试方法:
xlog分级输出:
xlog("L_DEBUG", "请求URI: $ru, 分支数: $bn\n");条件断点法:
if ($ru == "sip:debug@test.com") { xlog("L_ERR", "调试信息: $ru, $fu, $du\n"); drop(); }变量追踪技巧: 用
$var(trace)记录关键节点值,最后统一输出:$var(trace) = $var(trace) + "阶段1:" + $ru + "|";
4.2 性能陷阱与规避方案
伪变量虽方便,但误用会导致性能问题:
重复计算问题:
# 错误做法:每次循环都执行转换 for ($var(i)=0; $var(i)<10; $var(i)=$var(i)+1) { $(ru.user{lower}); } # 正确做法:预先转换存储 $var(lower_user) = $(ru.user{lower});正则表达式优化:
- 避免在循环中使用复杂正则
- 优先使用
{s.substr}等简单操作 - 对固定模式预编译
regex模块
内存缓存技巧: 对频繁访问的伪变量(如
$Ts),可以缓存到普通变量:$var(now) = $Ts;
5. 版本兼容性与升级指南
从Kamailio v5.2开始,伪变量系统有几个重要变化:
新增变量:
$Ctx上下文变量,替代部分全局变量用法$sht共享内存表访问更安全
废弃特性:
$avp的字符串索引方式(改用$(avp(name)))- 部分转换函数的旧语法(如
uri.escape改为escape.uri)
性能改进:
- PV模块的转换函数采用延迟计算
- 正则引擎升级到PCRE2
升级时建议:
- 先用
-c参数检查配置 - 逐步替换废弃语法
- 重点测试性能敏感路径
6. 真实案例:用伪变量构建智能路由
去年我们实现了一个基于地理位置的路由系统,核心逻辑如下:
# 从P-Asserted-Identity提取号码 $var(caller) = $(hdr(P-Asserted-Identity){uri.user}); # 查询号码归属地 if (!avp_db_query("SELECT region FROM numbers WHERE num='$var(caller)'", "$avp(region)")) { xlog("L_ERR", "归属地查询失败: $var(caller)\n"); } # 根据地域选择网关 switch ($avp(region)) { case "north": $du = "sip:gw1.example.com"; break; case "south": $du = "sip:gw2.example.com"; break; default: loadbalance("gw_pool"); }这个方案处理了每天数百万呼叫,关键就在于:
- 伪变量快速提取关键信息
- AVP变量保持跨事务状态
- 转换函数确保数据安全
7. 开发者必备的伪变量清单
这是我整理的常用伪变量速查表,打印出来贴在显示器旁能省不少时间:
表:Kamailio伪变量终极参考
| 分类 | 变量 | 描述 | 示例 |
|---|---|---|---|
| SIP消息 | $rm | 请求方法 | if ($rm == "INVITE") |
$rp | 响应码 | if ($rp == "487") | |
| URI处理 | $(uri.user) | 提取URI用户部分 | $(fu.user{uri.user}) |
$(uri.host) | 提取URI主机部分 | if ($(ru.host) == "vip.com") | |
| 头域处理 | $hdr(Content-Length) | 获取头域值 | if ($hdr(CL) > 1000) |
$hdr_e(name) | 检查头域存在性 | if ($hdr_e(Record-Route)) | |
| 系统信息 | $Tf | 当前微秒数 | $var(uid)=$Ts+$Tf |
$time(%S) | 格式化时间 | xlog("时间: $time(%H:%M)\n") | |
| 分支处理 | $bT | 分支标签 | t_on_branch("BR_$bT") |
$bS | 分支状态 | if ($bS == "2") |
最后分享一个冷知识:在Kamailio控制台输入cfg.dump 3可以查看所有伪变量的当前值,调试复杂路由时比xlog更高效。不过生产环境慎用,可能影响性能。