Featured image of post NAT_TRAVERSAL和NATHELPER模块介绍

NAT_TRAVERSAL和NATHELPER模块介绍

NAT 协议

NAT协议参考:RFC 2663

NAT For TCP: RFC 5382

NAT For UDP: RFC 4787

在介绍opensips的这两个模块使用之前,我们需要先了解一下NAT相关的概念。 NAT (Network Address Translation),网络地址转换,是一种网络地址转换技术, 主要是将内部的私有IP地址转换为公共IP地址,使得外部网络能够访问到内部的私有网络。

NAT 类型

静态NAT

一个公网IP对应一个私网IP,一对一转换,仅支持地址转换,不支持端口映射。 需要维护一个公共的地址池,目前已经不再使用。

NAPT

目前说的NAT基本上指的就是NAPT了,NAPT使用端口多路复用技术,不但要地址ip转换,还要端口映射。 主要有以下几类:

Full Cone NAT(完全锥形NAT)

特点:ip和port都不设限. 场景: 完全锥形 client 发送数据包给server1, 通过NAT设备完成192.168.0.1:3000<–>1.2.3.4:6000的映射。

server1收到数据包后,可以往1.2.3.4:6000发送数据包,server2也能往1.2.3.4:6000发送数据包。

Restricted Cone NAT(限制锥形NAT)

特点:ip设限,port不设限. 场景: 限制锥形 client 发送数据包给server1, 通过NAT设备完成192.168.0.1:3000<–>1.2.3.4:6000的映射。

server1收到数据包后,往1.2.3.4:6000发送数据包,此时NAT允许该server1通过数据。

但是client没有往server2发送数据,所以server2不能通过NAT设备1.2.3.4:6000发送数据包。

Port Restricted Cone NAT(端口限制锥形NAT)

特点:IP和port都受限. 场景: 端口限制锥形 client 发送数据包给server1, 通过NAT设备完成192.168.0.1:3000<–>1.2.3.4:6000的映射。

server1收到数据包后,往1.2.3.4:6000发送数据包,此时NAT允许该server1通过数据。

但是server1的其他端口(除了4000),都不允许通过NAT设备发送数据包,server2更不允许。

Symmetric NAT(对称NAT)

特点:对每个外部主机或端口的会话都会映射为不同的端口 对称NAT client 发送数据包给server1, 通过NAT设备完成192.168.0.1:3000<–>1.2.3.4:6000的映射。

server1的4000收到数据包后,往1.2.3.4:6000发送数据包,此时NAT允许该server1通过数据。

client 发送数据包给server1, 通过NAT设备完成192.168.0.1:3000<–>1.2.3.4:6001的映射。

server1端4001收到数据包后,往1.2.3.4:6001发送数据包,此时NAT允许该server1通过数据

server端只有client发送过数据的数据包,NAT才允许通过,并且NAT的端口映射会不一样。

NAT_TRAVERSAL 和 NATHELPER 介绍

这两个模块都是针对Client的NAT而设计的,一方面是让client知道自己的外网ip和port, 另一方面记录此NAT的ip和port,用于回复信令。

需要注意的是这两个模块无法让client知道opensips服务端信令和媒体的外网ip和port

NATHELPER的使用场景比NAT_TRAVERSAL要简单,但是功能更多。

功能 NATHELPER NAT_TRAVERSAL
支持设置ping间隔
支持设置ping方法
支持修改ping发出去的端口 x
支持tcp方式的ping x
修改Register的Contact fix_nated_register fix_contact
修改Invite的Contact fix_nated_contact fix_contact
修改sdp的o=ip fix_nated_sdp x
开始执行ping方法 sipping_bflag / ping_nated_only nat_keepalive

为啥需要ping? 当client通过NAT发送请求到opensips,为了保持连接,需要往NAT链路上发送ping包, 可以是OPTIONS也可以是INFO,能够保证NAT链路一直处于活动状态。

模块使用示例

opensips版本

opensips 3.3.10 (x86_64/linux)

NAT_TRAVERSAL

完整配置:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
###### Global Parameters #########

xlog_level=4
#debug_mode=yes
log_stderror=yes
#stderror_enabled=yes
#syslog_facility=LOG_LOCAL0
log_facility=LOG_LOCAL0

advertised_address=116.198.229.200
socket=udp:172.16.0.3:5271
socket=udp:172.16.0.3:5272

####### Modules Section ########

# set module path
mpath="/usr/local/lib64/sbc/modules/"


#### SIGNALING module
loadmodule "signaling.so"
loadmodule "db_mysql.so"

#### StateLess module
loadmodule "sl.so"

#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)

#### Record Route Module
loadmodule "rr.so"
modparam("rr", "append_fromtag", 0)

#### MAX ForWarD module
loadmodule "maxfwd.so"

#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"

#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)

loadmodule "httpd.so"
loadmodule "mi_http.so"

modparam("httpd","port",9998)

#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", "NAT")

#### REGISTRAR module
loadmodule "registrar.so"

#### RTPengine protocol
loadmodule "rtpengine.so"
modparam("rtpengine", "rtpengine_sock", "udp:172.16.0.3:22222")

#### Nathelper protocol
#loadmodule "nathelper.so"
#modparam("registrar|nathelper", "received_avp", "$avp(rcv)")

#### UDP protocol
loadmodule "proto_udp.so"
loadmodule "nat_traversal.so"
loadmodule "dialog.so"

modparam("nat_traversal", "keepalive_interval", 10)
modparam("nat_traversal", "keepalive_method", "OPTIONS")
modparam("nat_traversal", "keepalive_from", "sip:keepalive@my-domain.com")


####### Routing Logic ########

# main request routing logic
route{
	if (!mf_process_maxfwd_header(10)) {
		sl_send_reply(483,"Too Many Hops");
		exit;
	}

	if (has_totag()) {
		# sequential requests within a dialog should
		# take the path determined by record-routing
		if (loose_route()) {
			if (is_method("INVITE")) {
				# even if in most of the cases is useless, do RR for
				# re-INVITEs alos, as some buggy clients do change route set
				# during the dialog.
				record_route();
			}

			# route it out to whatever destination was set by loose_route()
			# in $du (destination URI).
			route(relay);
		} else {
			if ( is_method("ACK") ) {
				if ( t_check_trans() ) {
					# non loose-route, but stateful ACK; must be an ACK after
					# a 487 or e.g. 404 from upstream server
					t_relay();
					exit;
				} else {
					# ACK without matching transaction ->
					# ignore and discard
					exit;
				}
			}
			sl_send_reply(404,"Not here");
		}
		exit;
	}

	# CANCEL processing
	if (is_method("CANCEL")) {
		if (t_check_trans())
			t_relay();
		exit;
	}

	t_check_trans();

	#if (!is_method("REGISTER")) {
	#	if (is_myself("$fd")) {
	#		# if caller is not local, then called number must be local
	#		if (!is_myself("$rd")) {
	#			send_reply(403,"Rely forbidden");
	#			exit;
	#		}
	#	}
	#}

	# preloaded route checking
	if (loose_route()) {
		xlog("L_ERR",
		"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
		if (!is_method("ACK"))
			sl_send_reply(403,"Preload Route denied");
		exit;
	}

	# record routing
	if (!is_method("REGISTER|MESSAGE"))
		record_route();

	if (!is_myself("$rd")) {
		append_hf("P-hint: outbound\r\n");
		route(relay);
	}

	# requests for my domain
	if (is_method("PUBLISH|SUBSCRIBE")) {
		sl_send_reply(503, "Service Unavailable");
		exit;
	}

	# check if the clients are using WebSockets or WebSocketSecure
	if ( $socket_in(proto) == "WS"|| $socket_in(proto) == "WSS")
		setflag("SRC_WS");

	# consider the client is behind NAT - always fix the contact
	#fix_nated_contact();
	xlog("L_DBG","[$cfg_line][$ci]---main-1-:$rm|$rs|$tu|$socket_in(port)|$var(contact)\n");

	if (is_method("REGISTER")) {

		# indicate that the client supports DTLS
		# so we know when he is called
		if (isflagset("SRC_WS"))
			setbflag("DST_WS");

		#fix_nated_register();
		if (!save("location"))
			sl_reply_error();

		exit;
	}
	xlog("L_DBG","[$cfg_line][$ci]---main--:$rm|$rs|$tu|$socket_in(port)|$var(contact)\n");
	if ($rU==NULL) {
		# request with no Username in RURI
		sl_send_reply(484,"Address Incomplete");
		exit;
	}
	# do lookup with method filtering
	if (!lookup("location","m")) {
		t_newtran();
		t_reply(404, "Not Found");
		exit;
	}

	route(relay);
}

route[relay] {
	# for INVITEs enable some additional helper routes
	if (is_method("INVITE")) {
		t_on_branch("handle_nat");
		t_on_reply("handle_nat");
	} else if (is_method("BYE|CANCEL")) {
		rtpengine_delete();
	}

	if (!t_relay()) {
		send_reply(500,"Internal Error");
	};
	exit;
}

branch_route[handle_nat] {

	if (!is_method("INVITE") || !has_body("application/sdp"))
		return;
	$var(rtp_flag) = "replace-origin ";

        if (isflagset("SRC_WS") && isbflagset("DST_WS")) { #web->web
            $var(rtp_flag) = $var(rtp_flag) + " ICE=force-relay DTLS=passive";
        } else if (isflagset("SRC_WS") && !isbflagset("DST_WS")){ #web->sip
            $var(rtp_flag) = $var(rtp_flag) + " codec-strip-G722 codec-strip-CN codec-strip-red strip-extmap rtcp-mux-demux DTLS=off SDES-off ICE=remove RTP/AVP";
        } else if (!isflagset("SRC_WS") && isbflagset("DST_WS")) {#sip->web
            $var(rtp_flag) = $var(rtp_flag) + " rtcp-mux-offer generate-mid DTLS=passive SDES-off ICE=force RTP/SAVPF";
        } else if (!isflagset("SRC_WS") && !isbflagset("DST_WS")) #sip->sip
            $var(rtp_flag) = $var(rtp_flag) + "  DTLS=off SDES-off ICE=remove RTP/AVP";

	rtpengine_offer("$var(rtp_flag)");
}

onreply_route[handle_nat] {

	#fix_nated_contact();
	if (!has_body("application/sdp"))
		return;
	$var(rtp_flag) = "replace-origin ";
	if (isflagset("SRC_WS") && isbflagset("DST_WS")) #web->web
        $var(rtp_flag) = $var(rtp_flag) + " ICE=force-relay DTLS=passive";
    else if (isflagset("SRC_WS") && !isbflagset("DST_WS")) #web->sip
        $var(rtp_flag) = $var(rtp_flag) + " codec-strip-G722 codec-strip-CN codec-strip-red codec-strip-opus rtcp-mux-offer generate-mid DTLS=passive SDES-off ICE=force";
    else if (!isflagset("SRC_WS") && isbflagset("DST_WS")) #sip->web
        $var(rtp_flag) = $var(rtp_flag) + " rtcp-mux-offer generate-mid DTLS=passive SDES-off ICE=remove RTP/AVP";
    else if (!isflagset("SRC_WS") && !isbflagset("DST_WS")) #sip->sip
        $var(rtp_flag) = $var(rtp_flag) + "  DTLS=off SDES-off ICE=remove RTP/AVP";
	rtpengine_answer("$var(rtp_flag)");
}

以上的配置并没有开启NAT的pingfix_contact,它的效果就是:

使用MicroSIP软电话通过udp:116.198.229.200:5271注册用户1001, 通过opensips-cli客户端执行mi ul_dump, 能够看到注册信息为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    "Domains": [
        {
            "name": "location",
            "hash_size": 512,
            "AORs": [
                {
                    "AOR": "1007",
                    "Contacts": [
                        {
                            "Contact": "sip:1007@172.16.80.21:58364;ob",
                            "ContactID": "3784571799363132468",
                            "Expires": 299,
                            "Q": "",
                            "Callid": "f3c3991a65404450959d7f899fae1d80",
                            "Cseq": 30781,
                            "User-agent": "MicroSIP/3.21.6",
                            "State": "CS_NEW",
                            "Flags": 0,
                            "Cflags": "",
                            "Socket": "udp:172.16.0.3:5271",
                            "Methods": 8063
                        }
                    ]
                }
            ]
        }
    ]
}

注册的sip为: register

可以看到client的NAT是113.104.238.248:2717, 但是opensips保存的contact为sip:1007@172.16.80.21:58364;ob, 这个是软电话的内网ip, 两个不同内网的软电话通过opensips相互拨打,会不通。

  • fix_contact()

那么如何修改呢?在save之前,收到Register信令就调用fix_contact(), 具体为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if (is_method("REGISTER")) {
    # indicate that the client supports DTLS
    # so we know when he is called
    if (isflagset("SRC_WS"))
        setbflag("DST_WS");
    fix_contact();
    #fix_nated_register();
    if (!save("location"))
        sl_reply_error();
    exit;
}

此时,opensips-cli执行mi ul_dump, opensips保存的location为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    "Domains": [
        {
            "name": "location",
            "hash_size": 512,
            "AORs": [
                {
                    "AOR": "1007",
                    "Contacts": [
                        {
                            "Contact": "sip:1007@113.104.238.248:2717;ob",
                            "ContactID": "3784571799363126326",
                            "Expires": 297,
                            "Q": "",
                            "Callid": "62ec515e2ece4564b3388035343fee32",
                            "Cseq": 16774,
                            "User-agent": "MicroSIP/3.21.6",
                            "State": "CS_NEW",
                            "Flags": 0,
                            "Cflags": "",
                            "Socket": "udp:172.16.0.3:5271",
                            "Methods": 8063
                        }
                    ]
                }
            ]
        }
    ]
}

能够看到Contact信息改为了NAT的IP地址和port

  • nat_keepalive

fix_contact位置添加nat_keepalive();,配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if (is_method("REGISTER")) {
    # indicate that the client supports DTLS
    # so we know when he is called
    if (isflagset("SRC_WS"))
        setbflag("DST_WS");
    fix_contact();
    nat_keepalive();
    #fix_nated_register();
    if (!save("location"))
        sl_reply_error();
    exit;
}

重启服务后,能够看到每10s会往client的NAT 发送OPTIONS信令。 本地发出端口用的是.cfg里第一个默认的socket=协议,这块不能更改. ping

软电话坐席退出,ping还会执行的次数为:4min/keepalive_interval。 这个4分钟 目前没找到哪里配置,是实际测试的结果。

NATHELPER

配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
loadmodule "nathelper.so"
modparam("registrar|nathelper", "received_avp", "$avp(rcv)")
modparam("nathelper", "natping_interval", 10)
modparam("nathelper", "ping_threshold", 10)
modparam("nathelper", "max_pings_lost", 5)
modparam("nathelper", "natping_partitions", 4)
modparam("nathelper", "natping_socket", "172.16.0.3:5272") #修改发送ping的本地端口
modparam("nathelper", "sipping_bflag", "SIPPING_ENABLE")
modparam("nathelper", "sipping_from", "sip:pinger@siphub.net")
modparam("nathelper", "sipping_method", "OPTIONS")
  • fix_nated_register()

Register信令为例, 想要修改RegisterContact信息,在save之前,调用fix_nated_register(), 具体为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (is_method("REGISTER")) {
	# indicate that the client supports DTLS
	# so we know when he is called
	if (isflagset("SRC_WS"))
	        setbflag("DST_WS");	
	fix_nated_register();
	if (!save("location"))
	        sl_reply_error();	
	exit;
}

具体效果为:

  1. 保存的用户信息中新增了Received字段存储NAT信息,使用opensips-cli 里的mi ul_dump查看: ping contact信息并未变化
  2. 在给客户端返回200OKContact字段中增加received字段, ping
  • fix_nated_contact()

此时opensips保存的Register里的Contact会被修改成NAT的ip和port. ping

  • 如何开启ping?

nathelper有两种方式:

  1. ping_nated_only

这个参数是:Contact里带有behind_NAT标志,才会ping. 2. sipping_bflag

以本配置为例,设置的标志为:SIPPING_ENABLE, 那么在Register信令处, 要设置SIPPING_ENABLE标志。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
if (is_method("REGISTER")) {
	# indicate that the client supports DTLS
	# so we know when he is called
	if (isflagset("SRC_WS"))
	        setbflag("DST_WS");	
	setbflag("SIPPING_ENABLE");
	fix_nated_register();
	fix_nated_contact();
	if (!save("location"))
	        sl_reply_error();	
	exit;
}

效果为:

ping

如果坐席注销,nathelper立即就停止ping,比nat_traversal效果要好。

  • fix_nated_sdp() 参数
  1. 0x01 - adds “a=direction:active” SDP line;
  2. 0x02 - 重写sdp中的c=ip地址为NAT的ip地址;
  3. 0x04 - adds “a=nortpproxy:yes” SDP line;
  4. 0x08 - 重写sdp中的o=ip地址为NAT的ip地址;
  5. 0x10 - 重写所有的媒体ip

在需要修改sdp的Route里,执行此函数即可,sdp参数例子为:

sdp

本博客已稳定运行
发表了26篇文章 · 总计45.09k字
本站总访问量 次 · 您是本站第 位访问者
粤ICP备2025368587号-1| 使用 Hugo 构建
主题 StackJimmy 设计