OpenSIPS实战九:跨NAT通信

目录

前言

1、NAT问题简介

2、解决终端NAT问题

    2.1 使用终端的外网地址通信

    2.2 保活终端NAT路由映射

3、 媒体NAT处理

小结

 


 

前言

 

跨NAT通信在VOIP通信中常会出现问题,由其原生基于UDP传输导致。虽然现在也有基于websocket这样的基于TCP的传输方式,但整体上还是基于UDP的。而VOIP的很重要的应用场景是企业应用,所以很多时候通信终端都是处于内网之中,这也使得解决NAT问题更加重要。

 

1、NAT问题简介

 

NAT(Network Address Translation,网络地址转换),是一种实现内网中的主机与因特网上的主机通信的技术,该技术使用普遍,大到企业内网和小到日常的路由器等。企业内网通常通过一个NAT网关进行协议转换并建立内网与外网的连接会话,并由NAT网关负责连接会话的管理。如图

网关会对长时间没有通信的会话进行清理,回收IP和端口资源。对于基于TCP的服务而言,与外网服务保持长连接,似乎并没有太多通信的问题,因为TCP基于连接且有状态的。但对于无状态的UDP而言,服务端需要知道终端的外网地址,否则服务端的消息将无法返回给内网中的客户端。由于语音通信普遍使用基于UDP的SIP协议进行通信,往往使得NAT穿越成为问题。

 

2、解决终端NAT问题

 

终端的问题主要是两个,一个是内网中的终端只知道自己的内网地址,二是需要维持NAT地址的映射关系,不能让NAT网关清掉了。

 

2.1 使用终端的外网地址通信

内网中的终端是不知道自己的外网地址的,所以发起SIP REGISTER或其他消息的时候,Contact头域带的是终端的内网地址,如:

 

REGISTER sip:192.168.253.128:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.253.1:6090;branch=z9hG4bK-d87543-e4034a3570717061-1--d87543-;rport
Max-Forwards: 70
Contact: <sip:10086@192.168.253.1:6090;rinstance=76a144f7248850e7>
To: "10086"<sip:10086@192.168.253.128:5060>
From: "10086"<sip:10086@192.168.253.128:5060>;tag=39775242
Call-ID: c32d6d4b7004dc02@Rmlubi1QQw..
CSeq: 4 REGISTER
Expires: 3600
...

OpenSIPs默认是会试用Contact头域的地址作为终端的通信地址,所以这会导致终端收不到OpenSIPs返回的请求响应消息,当然也收不到呼叫请求。

要解决这个问题,就需要让OpenSIPs获取终端的外网地址,并将Contact中的URI地址替换为终端的外网地址并保存起来(Contact信息默认存在MySQL 数据库location表中),后续通信都以保存的外网地址通信。这个功能主要由OpenSIPs的nat_traversal模块提供,只需要在OpenSIPS路由脚本中加一下配置就可以实现:

 

loadmodule "nat_traversal.so"   #加载提供支持的模块
loadmodule "nathelper.so"

route{
    ...
    if (is_method("REGISTER")){
        ...
        if (client_nat_test("11")) {
            fix_contact();
        }

        fix_nated_register();
        
        if (!save("location")){
            sl_reply_error();
            exit;
        }
        exit;
    }
    ...
}

这样终端的注册信息中就保存了外网地址,解决了NAT通信问题。

主要函数解析:

client_nat_test()函数:用于检查SIP是否包含私有地址,参数有0x01、0x02、0x04、0x08,或这四个值中的多个按位或得到的值,示例中的参数11是0x01|0x02|0x08得到的值(0x04值的意义请参考文档),分别表示检查Contact头域地址是否是私有地址、检查Via头域地址和接收消息的终端实际IP地址是否一样,已经检查Contact头域地址是否和接收到消息的终端实际IP地址是否一样,如果检查满足任意一个就进行fix_contact操作。私有地址判断遵循RFC 1918和RFC 6598。

fix_contact()函数:使用实际收到消息的源IP和Port替换SIP消息Contact头域中的IP和Port,后续保存Dialog时就保存了实际的IP地址,用于后续消息交互,也就解决了终端的NAT通信问题。

fix_nated_register()函数:该函数由nathelper模块提供,主要解析源地址并在200 OK响应消息中的Contact头域中添加received字段,将终端的外网地址返回给终端。主要用于REGISTER请求,让终端得到自己的外网地址(如果终端需要用的话)。

 

此外,使用OpenSIPs模块导出的函数时,需要注意使用范围,fix_contact()和client_nat_test()导出函数能在REQUEST_ROUTE, ONREPLY_ROUTE, BRANCH_ROUTE这几个路由块中使用。而fix_nated_register()函数只能在 REQUEST_ROUTE块中使用。关于路由有疑问可以参考《OpenSIPS实战(三):路由脚本介绍与实战》

 

2.2 保活NAT路由映射

就像前面说的,网关会对长时间没有通信的会话进行清理,回收IP和端口资源,所以终端建立的NAT路由表需要进行保活。一般的SIP终端都有定期发送探测包,如我使用的eyeBeam和linePhone注册OpenSIPs成功后都会定期向OpenSIPs发送探测包,这样就能达到保活终端的NAT路由映射的功能。下面是OpenSIPs打印的收到探测包的日志和抓包抓到的探测包:

 

# OpenSIPs日志打印收到探测包
Jan 27 00:11:16 [3497] DBG:core:udp_read_req: probing packet received len = 4
Jan 27 00:11:17 [3496] DBG:core:udp_read_req: probing packet received len = 4

# ngrep抓到探测包#
U 192.168.253.1:6090 -> 192.168.253.128:5060 #3712
.
.
..............
#
U 192.168.253.1:5060 -> 192.168.253.128:5060 #3713
.
.
..............

 

但是,全由客户端主动发起探测有时候会比较被动,比如某个不标准的终端不会定期发起探测或探测间隔太长等。所以如果想要服务端掌握主动权的话,就需要由服务端主动发起探测请求。OpenSIPs同样支持这样的功能,只需在路由脚本中增加如下配置:

 

loadmodule "nat_traversal.so"          #加载提供支持的模块
modparam("nat_traversal", "keepalive_interval", 90)

route{
    ...
        if (($rm=="REGISTER" || ($rm=="INVITE" && !has_totag()) ) && client_nat_test("3"))
        {
            #设置会话定时器,定期发送保活消息
            nat_keepalive();
        }
    ...
}

主要函数解析:

nat_keepalive()函数:该函数会为该Dialog添加定时器,定时发送保活消息。默认的发送的是NOTIFY消息,可以通过模块参数修改,包括保活时间间隔设置。

下面是OpenSIPs发起的保活消息:

 

# OpenSIPs发起的定期保活的SIP NOTIFY消息
NOTIFY sip:192.168.253.1:6090 SIP/2.0
Via: SIP/2.0/UDP 192.168.253.128:5060;branch=z9hG4bK2384259
From: sip:keepalive@192.168.253.128;tag=32140b8
To: sip:192.168.253.1:6090
Call-ID: 4916e47f-57f4f97e-48@192.168.253.128
CSeq: 1 NOTIFY
Event: keep-alive
Content-Length: 0

SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.253.128:5060;branch=z9hG4bK2384259
Contact: <sip:192.168.253.1:6090>
To: <sip:192.168.253.1:6090>;tag=d52a6c49
From: <sip:keepalive@192.168.253.128>;tag=32140b8
Call-ID: 4916e47f-57f4f97e-48@192.168.253.128
CSeq: 1 NOTIFY
User-Agent: eyeBeam release 3015c stamp 27107
Content-Length: 0

 

这样,OpenSIPs主动保活的功能就实现完成了。

 

3、媒体NAT处理

 

前两小节讲的都是信令的NAT处理,现在来看下媒体流的NAT处理,这里讲的还是以OpenSIPs作为信令服务器而不是兼顾媒体服务而言的,也就是只涉及SDP协议的处理。

OpenSIPs媒体NAT处理不是很常见,一般终端NAT处理好了就没有什么问题了。但也遇到过,主要是在搞基于websocket的web电话的时候遇到过,SDP保存的地址使用的是内网地址。

OpenSIPs的nathelper模块导出函数fix_nated_sdp提供对SDP中的媒体IP进行修改的支持。

 

下面是修改实例,如果是基于websocket的请求,就修改SDP中的媒体地址和源地址为信令请求的源地址(实际的外网地址):

 

loadmodule "nathelper.so"          #加载提供支持的模块

route{
    ... 
    if (is_method("INVITE")) {
        ...

        if ( proto==WS)
        {
            fix_nated_sdp(“10”, , );
        }

        t_on_reply("modify_sdp");
    }
    ...
}

onreply_route[modify_sdp] {
    if(status=="200") {
       fix_nated_sdp(“10”, , );
    }
}

增加fix_nated_sdp函数处理后,主叫的SDP媒体地址(c=)和源(o=)中的地址将会被替换为消息的源地址后,再用于呼叫被叫。如果被叫的SDP也需要做修改,可以在ONREPLY_ROUTE脚本段中,判断被叫返回的200 OK,并增加fix_nated_sdp()函数进行转换,正如示例中做的那样。

fix_nated_sdp(flags [, ip_address [, sdp_fields]])函数解析:

flags必选参数,该值可以是以下值或以下值的按位或得到的值:

         0x01 --在SDP中增加“a=direction:active”行;

         0x02 --使用消息的源地址或者ip_address参数指定的Ip地址重写SDP中媒体IP地址(“c=”)。

         0x04--在SDP中增加”a=nortpproxy:yes”行。

         0x08 --使用消息的源地址或者ip_address参数指定的Ip地址重写SDP中源IP地址(“o=”)。

         0x10 --强制重写空的媒体IP和空的源IP地址。如果没有此标志,空IP将保持不变。

ip_address可选参数,指定用来重写SDP的IP,如果不指定该参数,默认使用信令消息的源地址。

sdp_fields可选参数,指定要附加到SDP消息后的sdp字段,每个sdp字段前面必须有“\r\n”

 

 

 从全文可以看出,OpenSIPs解决NAT通信问题非常简单,解决终端NAT问题并不难。如果这样还不能满足需求,可以参阅文档,寻找其他可能的实现。再不济可以考虑修改NAT相关模块的实现,提供新的函数实现,总是可以做到的。

 


(全文完)

 

更多查看官方文档

https://opensips.org/html/docs/modules/2.3.x/nat_traversal.html#idp5604720

https://opensips.org/html/docs/modules/2.4.x/nathelper.html#idp5641936

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值