UDP防火墙穿透原理与分析

本文详细介绍了P2P技术中如何通过UDP穿透NAT的基本原理,包括NAT的类型、ConeNAT与SymmetricNAT的区别,以及如何在NAT上建立连接通道,实现两个位于不同私有网络内的主机进行直接通信。

 

论坛上经常有对 P2P 原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码)。呵呵,在这里我就用自己实现的一个源代码来说明 UDP 穿越 NAT 的原理。
首先先介绍一些基本概念:
  NAT(Network Address Translators)
,网络地址转换:网络地址转换是在 IP 地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。 NAT 分为两大类,基本的 NAT NAPT(Network Address/Port Translator)
最开始 NAT 是运行在路由器上的一个功能模块。
最先提出的是基本的 NAT ,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪 90 年代中期提出的)。那么这个子网中其实只有少数的节点需要全球唯一的 IP 地址,其他的节点的 IP 地址应该是可以重用的。
因此,基本的 NAT 实现的功能很简单,在子网内使用一个保留的 IP 子网段,这些 IP 对外是不可见的。子网内只有少数一些 IP 地址可以对应到真正全球唯一的 IP 地址。如果这些节点需要访问外部网络,那么基本 NAT 就负责将这个节点的子网内 IP 转化为一个全球唯一的 IP 然后发送出去。 ( 基本的 NAT 会改变 IP 包中的原 IP 地址,但是不会改变 IP 包中的端口 )
  
关于基本的 NAT 可以参看 RFC 1631
  
  
另外一种 NAT 叫做 NAPT ,从名称上我们也可以看得出, NAPT 不但会改变经过这个 NAT 设备的 IP 数据报的 IP 地址,还会改变 IP 数据报的 TCP/UDP 端口。基本 NAT 的设备可能我们见的不多(呵呵,我没有见到过), NAPT 才是我们真正讨论的主角。看下图:
                    Server S1                 
                18.181.0.31:1235                 
                        |
      ^ Session 1 (A-S1) ^     |
      | 18.181.0.31:1235 |     |
      v 155.99.25.11:62000 v     |   
                        |
                        NAT
                      155.99.25.11
                        |
      ^ Session 1 (A-S1) ^     |
      | 18.181.0.31:1235 |     |
      v   10.0.0.1:1234   v     |
                        |
                      Client A
                    10.0.0.1:1234
  
有一个私有网络 10.*.*.* Client A 是其中的一台计算机,这个网络的网关(一个 NAT 设备)的外网 IP 155.99.25.11( 应该还有一个内网的 IP 地址,比如 10.0.0.10) 。如果 Client A 中的某个进程(这个进程创建了一个 UDP Socket, 这个 Socket 绑定 1234 端口)想访问外网主机 18.181.0.31 1235 端口,那么当数据包通过 NAT 时会发生什么事情呢?
首先 NAT 会改变这个数据包的原 IP 地址,改为 155.99.25.11 。接着 NAT 会为这个传输创建一个 Session Session 是一个抽象的概念,如果是 TCP ,也许 Session 是由一个 SYN 包开始,以一个 FIN 包结束。而 UDP 呢,以这个 IP 的这个端口的第一个 UDP 开始,结束呢,呵呵,也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个 Session 分配一个端口,比如 62000 ,然后改变这个数据包的源端口为 62000 。所以本来是( 10.0.0.1:1234->18.181.0.31:1235 )的数据包到了互联网上变为了( 155.99.25.11:62000 ->18.181.0.31:1235 )。
一旦 NAT 创建了一个 Session 后, NAT 会记住 62000 端口对应的是 10.0.0.1 1234 端口,以后从 18.181.0.31 发送到 62000 端口的数据会被 NAT 自动的转发到 10.0.0.1 上。(注意:这里是说 18.181.0.31 发送到 62000 端口的数据会被转发,其他的 IP 发送到这个端口的数据将被 NAT 抛弃)这样 Client A 就与 Server S1 建立以了一个连接。
呵呵,上面的基础知识可能很多人都知道了,那么下面是关键的部分了。
看看下面的情况:
  Server S1                         Server S2
18.181.0.31:1235                     138.76.29.7:1235
    |                               |
    |                               |
    +----------------------+----------------------+
                    |
  ^ Session 1 (A-S1) ^     |     ^ Session 2 (A-S2) ^
  | 18.181.0.31:1235 |     |     | 138.76.29.7:1235 |
  v 155.99.25.11:62000 v     |     v 155.99.25.11:62000 v
                    |
                  Cone NAT
                155.99.25.11
                    |
  ^ Session 1 (A-S1) ^     |     ^ Session 2 (A-S2) ^
  | 18.181.0.31:1235 |     |     | 138.76.29.7:1235 |
  v   10.0.0.1:1234   v     |     v   10.0.0.1:1234   v
                    |
                  Client A
                10.0.0.1:1234
  
接上面的例子,如果 Client A 的原来那个 Socket( 绑定了 1234 端口的那个 UDP Socket) 又接着向另外一个 Server S2 发送了一个 UDP 包,那么这个 UDP 包在通过 NAT 时会怎么样呢?
这时可能会有两种情况发生,一种是 NAT 再次创建一个 Session ,并且再次为这个 Session 分配一个端口号(比如: 62001 )。另外一种是 NAT 再次创建一个 Session ,但是不会新分配一个端口号,而是用原来分配的端口号 62000 。前一种 NAT 叫做 Symmetric NAT ,后一种叫做 Cone NAT 。我们期望我们的 NAT 是第二种,呵呵,如果你的 NAT 刚好是第一种,那么很可能会有很多 P2P 软件失灵。(可以庆幸的是,现在绝大多数的 NAT 属于后者,即 Cone NAT
好了,我们看到,通过 NAT, 子网内的计算机向外连结是很容易的( NAT 相当于透明的,子网内的和外网的计算机不用知道 NAT 的情况)。
但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是 P2P 所需要的)。
那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?首先,我们必须在内网的 NAT 上打上一个“洞”(也就是前面我们说的在 NAT 上建立一个 Session ),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比如: 192.168.0.10 )向外部的某个 IP( 比如: 219.237.60.1) 发送一个 UDP 包,那么就在这个内网的 NAT 设备上打了一个方向为 219.237.60.1 的“洞”,(这就是称为 UDP Hole Punching 的技术)以后 219.237.60.1 就可以通过这个洞与内网的 192.168.0.10 联系了。(但是其他的 IP 不能利用这个洞)。
呵呵,现在该轮到我们的正题 P2P 了。有了上面的理论,实现两个内网的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一个中间人来联系这两个内网主机。
现在我们来看看一个 P2P 软件的流程,以下图为例:

              Server S
219.237.60.1
                |
                |
  +----------------------+----------------------+
  |                               |
NAT A (
外网 IP:202.187.45.3)           NAT B ( 外网 IP:187.34.1.56)
  |   (
内网 IP:192.168.0.1)               | ( 内网 IP:192.168.0.1)
  |                               |
Client A (192.168.0.20:4000)         Client B (192.168.0.10:40000)

  
首先, Client A 登录服务器, NAT A 为这次的 Session 分配了一个端口 60000 ,那么 Server S 收到的 Client A 的地址是 202.187.45.3:60000 ,这就是 Client A 的外网地址了。同样, Client B 登录 Server S NAT B 给此次 Session 分配的端口是 40000 ,那么 Server S 收到的 B 的地址是 187.34.1.56:40000
此时, Client A Client B 都可以与 Server S 通信了。如果 Client A 此时想直接发送信息给 Client B ,那么他可以从 Server S 那儿获得 B 的公网地址 187.34.1.56:40000 ,是不是 Client A 向这个地址发送信息 Client B 就能收到了呢?答案是不行,因为如果这样发送信息, NAT B 会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数 NAT 都会执行丢弃动作)。现在我们需要的是在 NAT B 上打一个方向为 202.187.45.3 (即 Client A 的外网地址)的洞,那么 Client A 发送到 187.34.1.56:40000 的信息 ,Client B 就能收到了。这个打洞命令由谁来发呢,呵呵,当然是 Server S
总结一下这个过程:如果 Client A 想向 Client B 发送信息,那么 Client A 发送命令给 Server S ,请求 Server S 命令 Client B Client A 方向打洞。呵呵,是不是很绕口,不过没关系,想一想就很清楚了,何况还有源代码呢(侯老师说过:在源代码面前没有秘密 8 )),然后 Client A 就可以通过 Client B 的外网地址与 Client B 通信了。
注意:以上过程只适合于 Cone NAT 的情况,如果是 Symmetric NAT ,那么当 Client B Client A 打洞的端口已经重新分配了, Client B 将无法知道这个端口(如果 Symmetric NAT 的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,我们不推荐这种猜测端口的方法)。
下面是一个模拟 P2P 聊天的过程的源代码,过程很简单, P2PServer 运行在一个拥有公网 IP 的计算机上, P2PClient 运行在两个不同的 NAT 后(注意,如果两个客户端运行在一个 NAT 后,本程序很可能不能运行正常,这取决于你的 NAT 是否支持 loopback translation ,详见 http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt ,当然,此问题可以通过双方先尝试连接对方的内网 IP 来解决,但是这个代码只是为了验证原理,并没有处理这些问题),后登录的计算机可以获得先登录计算机的用户名,后登录的计算机通过 send username message 的格式来发送消息。如果发送成功,说明你已取得了直接与对方连接的成功。
程序现在支持三个命令: send , getu , exit
  
  send
格式: send username message
  
功能:发送信息给 username
  
  getu
格式: getu
  
功能:获得当前服务器用户列表
  
  exit
格式: exit
  
功能:注销与服务器的连接(服务器不会自动监测客户是否吊线)
代码很短,相信很容易懂,如果有什么问题,可以给我发邮件 zhouhuis22@sina.com 或者在 CSDN 上发送短消息。同时,欢迎转发此文,但希望保留作者版权 8- )。
最后感谢 CSDN 网友 PiggyXP Seilfer 的测试帮助
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值