NAT穿洞技术
一、首先,NAT穿洞技术是为了解决IPv4资源匮乏而提出的技术,全名叫做网络地址转换,
NAT是将私有地址转换为合法IP地址的技术,通俗的讲就是将内网与内网通信时怎么将内网私有IP地址转换为可在网络中传播的合法IP地址。NAT的出现完美地解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。
而NAT打洞的就是实NAT一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。其基本思想是这样的:让位于NAT后的两台主机都与处于公共地址空间的、众所周知的第三台server端相连,然后,一旦NAT设备建立好UDP状态信息就转为直接通信,并寄希望于NAT设备会在分组其实是从另外一个主机传送过来的情况下仍然保持当前状态。这项技术需要一个圆锥型NAT设备才能够正常工作。对称型NAT不能使用这项技术。
NAT分类: STUN标准中,根据内部终端的地址(LocalIP:LocalPort)到NAT出口的公网地址(PublicIP:PublicPort)的影射方式,把NAT分为四种类型:
1、Full Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。
2、Restricted Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。
3、Port Restricted Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。
4、Symmetric NAT: 内网主机建立一个socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1时,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1),如果内网主机同时用这个socket给外部主机2发送数据,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).如果NAT有多于一个公网IP,则PublicIP-1和PublicIP-2可能不同,如果NAT只有一个公网IP,则Port-1和Port-2肯定不同,也就是说一定不能是PublicIP-1等于 PublicIP-2且Port-1等于Port-2。此外,如果任何外部主机想要发送数据给这个内网主机,那么它首先应该收到内网主机发给他的数据,然后才能往回发送,否则即使他知道内网主机的一个(PublicIP,Port)也不能发送数据给内网主机,这种NAT无法实现P2P通信,但是如果另一方是Full Cone NAT,还是可以实现穿透的
二、NAT打洞原理
clientA 和clientB 都要与server连接,server知道A和B的内网地址及外网地址。
假设client A要向client B对话,但是A不知道B的地址,即使知道根据NAT的原理这个对话在第一次会被拒绝,因为client B的NAT认为这是一个从没有过的外部发来的请求.这个时候,A如果发现自己没有保存B的地址,或者说发送给B的会话请求失败了,它会要求server端让B向A打一个洞,这个B->A的会话意义在于它使NAT B认为A的地址/端口是可以通过的地址/端口,这样A再向B发送对话的时候就不会再被NAT B拒绝了.打一个比方来说明打洞的过程,A想来B家做客,但是遭到了B的管家NAT B的拒绝,理由是:我从来没有听我家B提过你的名字,这时A找到了A,B都认识的朋友server,要求server给B报一个信,让B去跟管家说A是我的朋友,于是,B跟管家NAT B说,A是我认识的朋友,这样A的访问请求就不会再被管家NAT B所拒绝了.简而言之,UDP打洞就是一个通过server保存下来的地址使得彼此之间能够直接通信的过程,server只管帮助建立连接,在建立间接之后就不再介入了.
以下考虑两种情况典型情况——主机分别位于不同NAT后和主机位于相同NAT后。
1、 主机位于不同NAT后面
如果两个主机A和B都有私网IP地址并且位于不同的NAT后面。P2P 应用程序运行在主机A和B以及服务器S上。假设主机A想要与主机B直接建立一个UDP会话。那么B所在的NAT通常将会丢弃这个消息,因为源地址和端口号与和他所发起的外部会话建立连接的目的地址和端口号不匹配。类似的,B也我发主动建立与A的连接。UDP穿孔的方式是:A发送UDP请求到B的公共地址,同时通过服务器S转发一个消息到达B,消息内容是要求B发送UDP请求到A的公共地址。A的请求直接到达B的公共地址的消息将会使得A所在的NAT在A的私有地址和B的公共地址之间建立一个新的通讯会话(即打一个孔),同时B的消息 (A通过S要求B发送的)到达A的公共地址将会使得B所在的NAT在B的私有地址和A公共地址之间建立一个新的通讯会话(也打了一个孔)[4]。一旦新的UDP会话在每一方向上都建立成功,那么主机A 和B就能够相互直接通信而不再需要借助服务器S了。
2、 主机位于相同NAT后面
两台主机处于相同NAT后,即双方在同一私网内的情况下。由于双方并不知道这种情况,主机A如果需要连接主机B,仍然向B的外网地址发起连接请求,采用打孔方案进行连接。只有在 NAT允许内部的网络主机能够和内部的其他主机进行UDP会话的情况下连接也是可以实现的,但是显然这种方式是完全没必要的。同理,当一台主机位于NAT后,另一台主机为公网地址时,这种方式虽然可以建立直接连接,但仍然显得过于麻烦[5]。
假设A想通过服务器S做为介绍人,发起对B的连接。A向S发出消息请求与B进行连接。S将B的公网endpoint(即公网ip和port)以及内网endpoint(即内网ip和port)发给A,同时把A的公网、内网的endpoints发给B。由A和B发往对方公网endpoint的 UDP数据包能否被对方收到,这取决于当前的NAT是否支持“发夹”转换(hairpin转换,也就是同一台设备,不同端口之间的UDP数据包能否到达,详见3.5节)。但是A与B往对方内网endpoint发送的UDP数据包是一定可以到达的,无论如何,内网数据包不需要路由,并且速度更快。A与B有很大的可能性采用内网的endpoint进行常规的p2p通信。
假定NAT设备支持“发夹”转换,应用程序也忽略由内网endpoint的连接,那么A、B会采用公网endpoint做为p2p通信的连接,这势必会造成数据包无谓地经过NAT设备,这是一种对资源的浪费。我们会在第六节讨论这种情况,毕竟支持“发夹”转换的NAT设备还远没有对“打洞”技术支持的NAT设备多。就目前的网络情况而言,应用程序在“打洞”的时候,最好还是把公网endpoint和内网endpoint都实验一下
三、使用UDP、TCP穿透NAT
使用TCP 协议穿透NAT 的方式和使用UDP 协议穿透NAT 的方式几乎一样,没有什么本质上的区别,只是将无连接的UDP 变成了面向连接的TCP 。值得注意是:
1、 B在向A打洞时,发送的SYN 数据包,而且同样会被NAT_A 丢弃。同时,B需要在原来的socket 上监听,由于重用socket ,所以需要将socket 属性设置为SO_REUSEADDR 。
A向B发送连接请求。同样,由于B到A方向的孔已经打好,所以连接会成功,经过3 次握手后,A到B之间的连接就建立起来了。具体过程如下:
1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
2、 A和B分别与S的【主连接】保持联系。
3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect,大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-B会纪录此次连接的源地址和端口号,为接下来真正的连 接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-B纪录了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。
我的理解是A如果请求连接B必须要知道双方的地址(此时就需要中间人C),A通过C请求B,B就向A打洞,但是B的打洞信息会被A遗弃但是NAT-B却记录下了A的地址,等A下次再发送信息后就不会被遗弃,连接成功
但是我现在有一个疑惑就是A怎么知道与B进行连接的?也就是C通过A所提供的什么信息能找到B
来源:http://bbs.pediy.com/showthread.php?t=131961
http://blog.csdn.net/yxz149/article/details/1517269
http://www.ietf.org/rfc/rfc3027.txtNAT手册
http://lustlost.blog.51cto.com/2600869/1177494 用python实现UDP打洞