Kamailio Transformations - 常用的伪变量转换函数

伪变量转换函数大部分在 pv 模块中处理,官方文档记录的很全面,但是给的例子比较少。本文列举比较常用的转换函数。

参数列表转换

{param.value,name[, delimiter]}

根据名字从参数列表里面找到对应的项目,并返回其值 name 是参数名字,delimiter 是定界符。

$var(x) = "a=1;b=2;c=3";
$var(c) = $(var(x){param.value,c}); // "3"

// 默认定界符是分号,但也可以指定其它定界符(逗号除外)
$var(x) = "a=1&b=2&c=3";
$var(c) = $(var(x){param.value,c,&}); // "3"

// 下面的代码是错误的,定界符不能用逗号
$var(x) = "a=1,b=2,c=3";
$var(c) = $(var(x){param.value,c,,}); // 错误提示是 invalid separator in transformation: value,c,,

这个函数非常有用,比如我们可以在 FreeSWITCH 里面配置一个网关,realm 指向 Kamailio ,contact-params 指向 IMS。

<param name="realm" value="192.168.1.100"/> <!--Kamailio-->
<param name="contact-params" value="gwaddr=192.168.1.200:5060"/> <!--IMS-->

Kamailio 的 request 路由可以这样写:

route[SRC_FREESWITCH] {
	$var(params) = $(sel(contact.uri.params)); // 取 contact 的参数
	$var(gwaddr) = $(var(params){param.value,gwaddr}); // 取网关地址
	$du = "sip:" + $var(gwaddr);
	t_relay();
	exit;
}

{param.valueat,index[, delimiter]}

根据索引从参数列表里面找到对应的项目,并返回其值,index 是索引号(从 0 开始),delimiter 是定界符。

$var(x) = "a=1;b=2;c=3";
$var(b) = $(var(x){param.valueat,1}); // "2"

字符串转换

{s.int}

字符串转整数

$var(x) = "1234";
$var(i) = $(var(x){s.int}); // 1234

$var(x) = "1234.2";
$var(i) = $(var(x){s.int}); // 出错

$var(x) = "s1234";
$var(i) = $(var(x){s.int}); // 出错

{s.numeric}

删除字符串的所有非数字部分,并转成整数。

$var(x) = "(040)1234/567-89";
$var(num) = $(var(x){s.numeric}); // 040123456789

{s.ftime,format}

根据参数格式化 pv 变量中的 epoch 时间,该参数必须是 strftime 格式化的字符串形式。

$var(local_time) = $(TS{s.ftime,%Y-%m-%d %H:%M:%S}); // 例子:"2023-04-11 15:41:59"

{s.unquote}

删除字符串里面的单引号对和双引号对。

$var(x) = "'alice'";
$var(alice) = $(var(x){s.unquote}); // "alice"

$var(x) = "'alice";
$var(alice) = $(var(x){s.unquote}); // "'alice",没有变化,因为单引号没有成对

{s.unbracket}

删除字符串里面的 () 对、 [] 对、 {} 对以及 <> 对。

$var(x) = "<sip:alice@test.sip>";
$var(uri) = $(var(x){s.unbracket}); // "sip:alice@test.sip"

用这个函数来处理 Contact 头比较方便

URI 转换

$var(contact) = "sip:1001@192.168.1.100:5080;transport=udp;a=1;b=2";

$var(a)=$(var(contact){uri,param,a}); // "1"
$var(c)=$(var(contact){uri,param,c}); // ""
$var(a)=$(var(contact){uri.params}); // "transport=udp;a=1;b=2"
$var(a)=$(var(contact){uri.host}); // "192.168.1.100"
$var(a)=$(var(contact){uri.port}); // "5080"
$var(a)=$(var(contact){uri.user}); // "1001"
$var(a)=$(var(contact){uri.scheme}); // "sip"

多行文本转换

{line.count}

返回多行文本的行数。

{line.sw,match}

返回以 match 开头的行。

# 字符串赋值
$var(sdp) = "v=0\r\n";
$var(sdp) = $var(sdp) + "o=freeswitch 1680834355 1680834356 IN IP4 192.168.1.100\r\n";
$var(sdp) = $var(sdp) + "s=freeswitch\r\n";
$var(sdp) = $var(sdp) + "c=IN IP4 192.168.1.100\r\n";
$var(sdp) = $var(sdp) + "t=0 0\r\n";
$var(sdp) = $var(sdp) + "m=audio 2560 RTP/AVP 8 101\r\n";
$var(sdp) = $var(sdp) + "a=rtpmap:8 PCMA/8000\r\n";
$var(sdp) = $var(sdp) + "a=rtpmap:101 telephone-event/8000\r\n";
$var(sdp) = $var(sdp) + "a=fmtp:101 0-15\r\n";
$var(sdp) = $var(sdp) + "a=sendrecv\r\n";
$var(sdp) = $var(sdp) + "a=ptime:20\r\n";

$var(media) = $(var(sdp){line.sw,m=audio}); // 得到的值是 "m=audio 2560 RTP/AVP 8 101",$var(sdp) 是多行文本,line.sw 函数找到包含 m=audio 的那一行
$var(count) = $(var(sdp){line.count});  // 得到的值是 11 ,`$var(sdp)` 有 11 行

这个函数有用,比如下面的路由代码把 sdp 里面的 ptime 属性强制修改成 10 (尽管很少这么做)。

loadmodule "textops.so"
loadmodule "textopsx.so"
loadmodule "sdpops.so"

...

route[MODIFY_PTIME_10] {
	if (has_body("application/sdp")) {
		$var(body) = $sdp(body);
		$var(ptime_line) = $(var(body){line.sw,a=ptime});
		$var(new_ptime_line) = "a=ptime:10";
		replace_body($var(ptime_line), $var(new_ptime_line));
		msg_apply_changes();
		xinfo("newsdp = $sdp(body)\n");
	}
}

正则表达式替换

{re.subst,expression}

此转换类由 textops 模块导出,对伪变量执行 POSIX 正则表达式替换。

$var(s) = "9123456789";
$var(s1) = $(var(s){re.subst,/^9(.*)/\1/}); // 去掉字冠 9,得到 123456789

再举一个比较实用的例子:

$var(contact) = '"XiaoYingTao" <sip:6753997@xwitch.cn:5060>';
$var(uri) = $(var(contact){re.subst,/^.*<(sip:.*)>/\1/});  // 得到 sip:6753997@xwitch.cn:5060

还有一个例子:

$var(sdp) = "v=0\r\n";
$var(sdp) = $var(sdp) + "o=- 1704250570 3 IN IP4 192.168.1.100\r\n";
$var(sdp) = $var(sdp) + "s=-\r\n";
$var(sdp) = $var(sdp) + "c=IN IP4 192.168.1.100\r\n";
$var(sdp) = $var(sdp) + "t=0 0\r\n";
$var(sdp) = $var(sdp) + "m=audio 30608 RTP/AVPF 8\r\n";
     
$var(new_sdp) = $(var(sdp){re.subst,/AVPF/AVP/g}); # 把 sdp 里面的 AVPF 替换成 AVP

json 转换

此转换类由 json 模块导出。

loadmodule "json.so"

...

$var(j) = '{"a":"1", "b":2}';
$var(a) = $(var(j){json.parse,a}); // "1"
$var(b) = $(var(j){json.parse,b}); // "2" ,字符串,不是整数
$var(c) = $(var(j){json.parse,c}); // ""