穿越中间设备(middleboxes)的P2P通讯

Internet草案                                                              B. Ford

文档名:    draft-ford-midcom-p2p-01.txt                                       M.I.T.

截止有效期: April 27, 2004                                               P. Srisuresh

                                                          Caymas Systems

                                                                D. Kegel

                                                               kegel.com

                                                            October 2003

 

 

 

穿越中间设备(middleboxes)P2P通讯

 

 

 

 

本篇备注的状态

 

 

   该文档是一个internet草案,遵守RFC201610节的规定。Internet草案是IETF和他的工作组的文档。注意,其他团体或组织也可以发表工作文档为Internet草案。

 

 

   Internet-草案是一种草案文档,有效期最多是6个月。他可以随时被其他文档更新,替换或者废除。把Internet草案作为资料引用或者参考而不是在他的上面进一步工作是不适当的。

 

 

   这份internet草案可以通过这个地址获得:http://www.ietf.org/1id-abstracts.html

 

 

   internet草案的目录列表可以从这里获得:http://www.ietf.org/shadow.html

 

 

   发布本篇文档不受任何限制。

 

 

版权注意事项

 

 

   Copyright (C) The Internet Society (2003).  All Rights Reserved.

 

 

 

 

摘要

 

 

   本草案介绍了当前p2p应用程序在有中间设备进行通讯时的方法,这些中间设备比

   如:防火墙、网络地址转换器(NAT)。此外,本篇还提供给应用程序设计者和中间

   设备的实现者一些建议,使得他们能够即时的广泛的确定p2p的应用程序需要何种

   具体的代理、中继协议。

 

 

 

 

 

 

 

 

 

 


目录

 

 

   1.  介绍 ………………………………………………………………….……………………2

   2.  术语 ……………………………………………………………………………………….3

   3.  P2P穿越中间设备通讯的技术 …………………………………………………………..6

       3.1.  中继 ………………………………………………………………………………..6

       3.2.  反向连接 …………………………………………………………………………..7

       3.3.  UDP 穿孔 …………………………………………………………………………8

             3.3.1.  对端在不同的NATs背后 ……………………………………….………8

             3.3.2.  对端在相同的NATs背后 …………………….………………….…..…9

             3.3.3.  对端由多个NATs分开 ………………………………………….…….11

             3.3.4.  一致端口绑定 …………………………………………………….…....11

       3.4.  UDP 端口号预测 ………………………………………………………….…….11

       3.5.  同时打开TCP …………………………………………………………….………13

   4.  应用程序设计指南 …………………………………………………………….…..……13

       4.1. 有中间设备的怎么做 …………………………………………………….…...…..13

       4.2. 在同一个NAT背后的应用程序 ………………………………………….………13

       4.3. 发现对端 ….……………………………………………………………….………13

       4.4. TCP P2P 应用程序 ………………….……………………………….……………13

       4.5. 使用midcom协议 ………………….……………………………….…….………14

   5.  NAT设计指南 ……………………………………………………….………….………14

       5.1. 不赞成使用对称NATs(symmetric NATs) ……………………….….…….………15

       5.2. 给对称NATs设备添加Cone-NAT支持 ……………………….….…….………15

       5.3. 维护UDP端口绑定的一致端口 ……………………………….…...…….……...15

             5.3.1.  保留端口号 ………………………………………….…...……….……15

       5.4. UDP端口维护一致的端口绑定 ……………………………….…...….………16

       5.5. P2P应用程序大的超时时间 …..…………………………….…….………………16

   6.  安全考虑 ……………………………………………………………………...…………16

 

 

 

 

1. 介绍

 

 

 

   当前的internet到处都设有中间设备(middleboxes),比如网络地址转换器,这主要是由于IPv4的地址空间耗尽所引起的。然而,由中间设备建立起来的非对称的连通体系给p2p应用程序和协议带来了独特的问题,比如电信会议和多人在线游戏。由于NAT可能频繁的用于和IPv4的兼容上,这些问题也可能存在于IPv6的世界。就算NAT不再需要,防火墙也势必存在。

 

 

   当前的中间设备主要是为了典型的C/S模型设计的,中间设备中的匿名客户端来访问有固定IPserverDNS。许多的中间设备完成了这种非对称连接。私有网络中的主机能够发出一个初始连接到公网主机,但是外部主机不能初始发出对内网主机的连接,除非由中间设备的管理员配置过了。在普通的NAPT情况下,一个内网的Client不具有在公网唯一的IP地址,而是和同在一个私有网络中的主机共享一个IP地址,这种共享由NAPT来维护。这种对内网主机的匿名和不可访问性,对客户端软件比如web浏览器却不是问题,因为他们仅仅要需要初始向外的连接。不可访问性有时候被视为隐私的好处。

 

 

   然而,在p2p模型中,被视为clientinternet主机需要彼此直接建立连接会话。发起者和回应者也许在不同的中间设备的后面,可能既没有固定的IP,也没有公共网络中其他的存在形式。比如一个普通的在线游戏体系结构是参与主机来连接已知的服务器来初始化和其他的一些管理动作。紧接着,主机间建立了直接的连接,以便游戏期间快速有效的更新。类似的,一个文件共享应用程序也是连接已知的服务器进行资源查找,但对数据传送则建立直接的连接。中间设备产生了P2P连接的问题在于:中间设备背后的主机没有在internet上永久可用的端口和其他的客户建立直接的连接。RFC 3235 [NAT-APPL]简要的描述了这个问题,但是没有提供一般的解决方案。

 

 

   这篇文档中我们将提出p2p/中间设备问题的2种解决方法。首先我们概述p2p应用程   序在当前的中间设备中已知的工作方法。第二,我们提出一些使得能够在当前的中间设备布置情况下更健壮的应用程序设计指导。进一步,我们提出未来中间设备设计指导,使得他们更有效的支持p2p应用程序。我们的目标是使得需要穿越中间设备的应用程序及时而广泛。

 

 

2. 术语

 

 

 

本节中我们提出一些中间设备的术语,主要关注产生P2P应用程序问题的2种类型的中间设备。

 

 

 

 

   防火墙/Firewall

      防火墙限制了内网和外网的通讯,通常是丢弃认为是未授权的包。防火墙检查      但是不修改穿过边界包的IP地址和TCP/UDP端口信息。

 

 

   网络地址转换器/Network Address Translator (NAT)

      网络地址转换器不但检查而且修改穿越边界包的头信息,运行在NAT设备后面的      许多主机来共享较小数量的公网IP地址(通常是一个)

 

 

   网络地址转换器有2中形式:

 

 

   基本NAT/Basic NAT

      当数据包穿过NAT时,基本NAT映射内部主机的私有IP为公网IP,不改变TCP/UDP      端口号。仅仅当基本NAT有一个公网IP地址池,它才发生效用,从池中做代表内部主机的地址绑定。

 

 

   网络地址/端口转换器(NAPT)

      现在最普通的端口转换器,他检查并且修改穿越边界包的IP地址和TCP/UDP端口      号,多个内部主机同时共享一个公网IP

 

 

   参考[NAT-TRAD][NAT-TERM]取得关于NAT分类和术语的更多信息。进一步划分NAPT的术语在最近的[STUN]文档中。当内部主机通过NAPT打开一个向外的TCPUDP会话时,NAPT分配给这个会话一个公共IP地址和端口,以便接下来外部节点的响应包能够被 NAPT接收、解释、转发给内部节点。效果就是NAPT建立了一个如下的端口绑定:(private IP addr, private port number)(public IP addr, public port number)。端口绑定定义了地址转换,NAPT将完成这个会话过程。一个和p2p应用程序相关的问题是当同一个内部主机同时发起和外网多个主机进行会话时,NAT做了些什么?

 

 

   锥形NAT/Cone NAT

      在建立了(private IP, private port)(public IP, public port)这个端口绑定之后,锥形NAT对来自同一个私有ip地址和端口的应用程序发起的一系列的会话进行端口重用,只要有一个会话还在,绑定的端口就保留。

    

      比如,设Client A在下面的模型中通过一个Cone NAT同时发起2个向外的会话。      他们都是来自同一个内部节点(10.0.0.1:1234)2个不同的外部服务器S1S2(译注:这里10.0.0.1:1234同时发起2个到外部server的连接是可能的,首先在于这2个可能是2个不同的协议,其次同一个地址同一个协议也可能将2socket绑定到同一个端口上面,比如每一个VStudio IDE进程就会在本机的同一个UDP端口建立一个socket)cone NAT只分配一个节点映射(endpoint tuple)给这两个会话,155.99.25.11:62000,确保了client端的端口号的一致性在地址解析时得到维护。由于basic NATs和防火墙在包流过中间设备时不修改端口号,所以他们可以看作是cone NATs的退化形式。

 

 

           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

 

 

   对称NAT / Symmetric NAT

      作为对比,对称NAT在内外网端口(private IP, private port) and (public IP, public port)绑定时并不维护一致的绑定端口。相反,他会给每一个新的会话分配一个新的端口。比如,Client A和上面一样发起2个和s1s2的会话连接,对称NAT可能会把session 1映射成155.99.25.11:62000,把session 2映射成这样155.99.25.11:62001NAT能够区分2个不同的会话,甚至当client应用程序的节点标识被丢失时,因为会话涉及的外部节点不同。

 

 

           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:62001 v

                                |

                           Symmetric 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

 

 

   cone NAT相对于symmetric NAT一样会产生TCP and UDP的拥塞问题。

 

 

   根据NAT能够有多少的自由接收已经建立连接(public IP, public port)的输入数据,Cone NAT可以被进一步划分。这种分类通常仅用于UDP方式数据交换,因为NAT和防火墙会无条件拒绝tcp连接,除非在其他地方有特别的设置。

 

 

   完全的(Full) Cone NAT

      在为一个向外的会话建立了公/私端口绑定之后,full cone NAT随后将接收来自公网上任何终端节点相应端口的输入通讯。Full cone NAT有时也称为“混杂”(promiscuous)NAT.

 

 

   受限的(Restricted) Cone NAT

      受限的cone NAT仅仅在外部输入数据包的端口匹配内部主机曾经发给外部数据时使用的映射端口才转发给内部主机。受限的cone NAT精简采用了防火墙的原则,通过限制输入数据包为已知的外部IP地址,来拒绝一些外部的主动连接请求。

 

 

   端口受限(Port-Restricted) Cone NAT

      反过来,对端口受限(Port-Restricted) Cone NAT,如果外部ip地址和端口匹配那些内部主机曾经向外发包在NAT上映射的端口,他就将这个输入包转发给相应的内部主机。当端口受限Cone NAT在维护这种穿越映射所需的端口身份表时,他提供了和对称(symmetric)NAT一样的对(未有映射的)主动的外部请求有同样的保护。

 

 

   最后,在这篇文档中我们定义一些新的术语来分类中间设备的P2P相关的行为。

   P2P-应用程序

      如本篇提到的,P2P应用程序是这样的程序:每个p2p的参与者在服务器上注册,接下来,在私有或者公共的终端建立对等(peering)的会话。

 

 

   P2P-中间设备(Middlebox)

      P2P-中间设备(Middlebox)就是允许应用程序穿越的中间设备。

 

 

   P2P-防火墙(firewall)

      P2P-firewall是一个P2P-中间设备(Middlebox),他提供防火墙功能,但是不做地址转换。

 

 

   P2P-NAT

      P2P-NATP2P-中间设备(Middlebox),他提供NAT功能,并且也提供防火墙功能。至少,P2P-Middlebox要为UDP通讯实现Cone NAT功能,以允许应用程序使用UDP穿孔 (UDP hole punching technique)技术建立健壮的P2P连接。

 

 

   回路解释/Loopback translation

      当一个NAT设备的内部主机使用公共地址(译注:就是那个内部主机对外表现的      地址)试图连接在同一个NAT设备后的内部主机,NAT设备会按照如下做2次解释("Twice-nat" translation):原主机私有终端地址被解释成公共终端地址,在包被转发以前,目标主机的公共终端地址又被解释成私有终端地址,我们称由NAT完成的这种解释为“回路解释”(Loopback translation)(译注:这里实际是说NAT在转发数据时如果会先查一下,看看目的终端的公共地址是不是自己,如果是,就表示源、目的主机都在本NAT里面。)

 

3. 跨越中间设备P2P通讯的技术

 

 

 

   本节从应用程序和协议设计的观点,来详细的回顾当前p2p跨越中间设备通讯实现的技术。

 

 

3.1. 中继/Relaying

 

 

   在有中间设备的p2p通讯的实现方法中,最可靠,但缺乏效率的方法是:使P2P通讯看起来象通过中继的C/S网络。比如,2个客户端AB,初始发起了TCPUDP的连接到一个已知的有永久IP地址的server S。然而,客户端是分居在两个私有网络中的,他们各自的中间设备阻止了一方直接向另一方发起连接。

 

 

                         Server S

                            |

            +----------------------+----------------------+

            |                                |

          NAT A                           NAT B

            |                                |

         Client A                          Client B

 

 

   两个客户端可通过server S来中继他们的消息,而不是试图直接连接。比如,要发消息给BA只要把消息通过已经建立的连接发给S,然后S使用已经建立的连接发给B

 

 

   这个方法有他的优点,只要双方都连上了server,他总能工作。他的明显的不足在于:他无谓的消耗了服务器的处理能力和网络带宽,而且,即使和服务器连接的很好,双方的通讯延时也增加了。TURN 协议 [TURN] 实现了一种相对可靠的中继方式。

 

 

3.2. 反向连接/Connection reversal

 

 

   如果仅有一个客户端在中间设备后面,那么第二项技术就可行了。比如,客户端A在一个NAT后面,而B有一个全球可路由的IP地址,如下图:

 

 

                        Server S

                    18.181.0.31:1235

                            |

                            |

            +----------------------+----------------------+

            |                                |

          NAT A                             |

    155.99.25.11:62000                        |

            |                                |

            |                                |

         Client A                          Client B

      10.0.0.1:1234                      138.76.29.7:1234

 

 

   客户端A的私有IP10.0.0.1,应用程序使用TCP1234端口。这个客户端和IP地址为18.181.0.31,端口为1235server建立了连接。NAT分配了62000TCP端口在他的公网IP155.99.25.11上,作为一个临时的终端地址为AS的会话提供服务,因此,S就认为A155.99.25.11:62000上。然而,对客户端B,他有自己的永久IP138.76.29.7,且Bp2p应用程序正在TCP端口1234Listen

 

 

   现在假设客户端B要发起一个和A的初始连接会话。B首次尝试连接A要么在A自认   为的地址10.0.0.1:1234,要么在server S看到的155.99.25.11:62000。然而,这两种情况都要失败。在第一种情况下,到10.0.0.1的包简单的被网络丢弃,因为10.0.0.1不是一个可以路由的公网地址。在第二种情况,来自于BTCP SYN请求会直接到达ANAT62000端口,但是ANAT会拒绝这个连接请求,因为仅仅向外的连接才被许可。

 

 

   在试图和A建立连接失败后,B可以借用S来中继一个给A的请求,告诉A做一个反向的到B的连接。A根据从S受到的这个请求,打开一个TCP连接到Bip:portANAT会许可这个连接,因为他从防火墙内发起,并且B能够收到这个连接,因为他不在任何的中间设备后面。

  

   许多当前的P2P系统实现了这一技术。当然,他的主要限制在于:仅有一端在NAT后面的情况才适用,在越来越普遍的两端都在NATs后面的情况,就不行了。

  

   因为反向连接不是一般的解决方案,不推荐作为一个主要策略。应用程序可以选择进行反向连接,但是当正向和反向都失败时,他应能回来再用其他的方法,比如通过中继。

 

 

3.3. UDP穿孔(hole punching)

 

 

   第三种技术,也是本文主要的兴趣之一,就是被称为UDP穿孔的技术。UDP穿孔依赖   于公共防火墙的属性和Cone NATs许可适当设计的p2p程序来穿孔以穿越中间设备建立彼此的直接连接,即使当通讯双方都在中间设备的后面。这项技术在RFC 30275.1[NAT-PORT]有简要的介绍,也在Internet的其他地方有非正式的描述[KEGEL], 并且被用在最近的一些协议种[TEREDO ICE]。不幸的是,和他的名字所暗示的一样,这个技术仅仅适用UDP

 

 

   我们将要考虑2种具体的情况,和应用程序如何以优雅的设计来处理这两种情况。在第一种情况中,代表了一种一般的情况,两个要建立P2P通讯的客户端被置于两个不同的NATs之后。第二种情况是,两个要建立P2P通讯的客户端在同一个NATs的后面,但是未必知道他们的这个处境。

 

 

3.3.1. 两端在不同的NATs之后

 

 

   假定clients A B 都有自己的私有IP地址,并且在不同的NATs之后。在A,B,S上运行的应用程序都使用UDP1234端口。AB都已和S建立了UDP连接,使得NAT AAS的会话分配了公网的UDP端口62000NAT BBS的会话分配了公网UDP端口31000

 

 

                       Server S

                     18.181.0.31:1234

                            |

                            |

            +----------------------+----------------------+

            |                                |

          NAT A                            NAT B

    155.99.25.11:62000                    138.76.29.7:31000

            |                                |

            |                                |

         Client A                          Client B

      10.0.0.1:1234                       10.1.1.3:1234

 

 

   现在假定A要和B建立P2PUDP连接。如果简单的发给B的公网地址138.76.29.7:31000, 那么NAT B通常会丢弃这个输入信息(除非他是full cone NAT),因为源地址和端口不是S---这个始发向外已经建立的地址和端口, 同样如果B简单的向NAT A 发送一个UDP包,也会被NAT A 丢弃。

 

 

   然而,假定AB的公网地址发一个UDP包,同时请求S要求BA的公网地址发一个UDP包到A的公网地址。AB的公网地址(138.76.29.7:31000)发送一个消息的同时导致NAT A为这个新的A的私有地址到B的公网地址建立了一个会话(映射)。与此同时BA的公网的消息在BNAT B上建立了一个新的B的私有地址到A的公网地址(155.99.25.11:62000)的会话(映射),一旦这个新的UDP会话在两端打开,AB的通讯就可以直接建立,而不再需要S进行“介绍”的负担。

 

 

   UDP穿孔技术有许多有用的特性。一旦在2个中间设备背后的主机建立了P2PUDP连接,这个连接的双方都可以充当“介绍人”的角色来帮助对端主机和其他主机建立P2P连接,这样可以最小化server来帮助建立初始连接的负荷。 应用程序不需要试图去检测他在什么样的中间设备背后,因为如果一端或者两端不在中间设备的后面,上层的程序依然能够很好的建立P2P连接。穿孔技术甚至可以自动在有多层NATs的情况下工作,就是一端或者双方都在两层或者超个两层的NATs后面的情况。

 

 

3.3.2. 双端在同一个NAT之后

 

 

   现在考虑双端在同一个NAT之后的情况(他们可能并不知道),这样他们就在同一个私有的IP地址空间中。AS建立了UDP会话,NAT分配了62000端口,BS建立了UDP连接,NAT分配了62001端口。

 

 

                       Server S

                    18.181.0.31:1234

                           |

                           |

                         NAT

                A-S 155.99.25.11:62000

                 B-S 155.99.25.11:62001

                            |

            +----------------------+----------------------+

            |                               |

         Client A                         Client B

      10.0.0.1:1234                      10.1.1.3:1234

 

 

   假定AB使用上面提到的UDP穿孔技术,通过S为介绍人来建立P2P连接。然后AB会知道彼此的公网IP地址和端口(S获得告知的),接着开始在那个公网IP和端口上发送UDP消息。只要NAT允许内网主机来开启转译的和内网主机的会话,而不仅仅是和外部主机,两个客户端就可以建立彼此的通讯。我们称这种情况为“回路解释”(loopback translation),因为包到达NAT被解释,然后再回到了内网,而不是送到了公网。例如,当A发送一个UDP包给B的公网地址,包的源地址和端口是10.0.0.1:124,目的端口和地址是155.99.25.11:62001NAT收到这个包,把他解释为源地址和端口是155.99.25.11:62000 (A的公网地址),目的地址和端口是10.1.1.3:1234,然后转发给B。即使NAT支持“回路解释”,   很显然,这种情况下的解释和转发都是不必要的,并可能增加AB的潜在的对话同时加重了NAT的负担。

 

 

   对这个问题的解决方法是直接转发(straightforward)。当AB通过S初始交换地址的时候,他们应该包括自己看到的自己的IP地址和端口和由server看到的自己的IP地址和端口。客户端然后同时给这两个地址发送数据,并使用第一个成功进行通讯的地址。如果两个客户端在同一个NAT的后面,那么,直接发往私有地址的包会先到达,就产生了一个直接连接的通道,而与NAT无关了。如果两个客户端在不同的NATs后面,直接发往私有地址的包就根本不能到达,但客户端将有希望通过各自的公网地址建立连接。然而,对这些包进行某种授权是重要的,因为在不同NATs的情况中,A直接发往B私有地址的信息完全可能到达和A同一个私网的其他主机(or vice versa)

 

 

3.3.3. 对端由多个NATs分开

 

 

   在某些涉及多个NAT设备的拓扑中,在没有具体的拓扑的知识情况下,两个客户端不   可能建立起来最优的P2P路由。考虑下面的情况:

 

 

                        Server S

                     18.181.0.31:1234

                            |

                            |

                          NAT X

                  A-S 155.99.25.11:62000

                  B-S 155.99.25.11:62001

                            |

                            |

            +----------------------+----------------------+

            |                                |

          NAT A                           NAT B

    192.168.1.1:30000                   192.168.1.2:31000

            |                                 |

            |                                 |

         Client A                           Client B

      10.0.0.1:1234                       10.1.1.3:1234

 

 

   假定NAT X是一个由ISP布置的大的工业的NAT,来为许多客户提供服务,NAT ANAT B是较小的客户NAT gateways。仅仅server SNAT X有全球可路由的IP地址。NAT ANAT B使用的公网IP地址实际上是在ISP的私有IP地址范围,而客户端AB又分别在NAT ANAT B的私有地址范围。每一个客户端象前面一样初始发出一个到S的连接,致使NAT ANAT B分别创建了一个公/私地址转换,又使得NAT X为每一个会话创建了一个公/私地址转换。

 

 

   现在假定AB试图建立一个P2P UDP连接。最优的情况是,A发送一个消息给BNAT B上面的公网地址(译注:这里沿用前面的译法,实际上是指映射后的地址),即在ISP的地址范围192.168.1.2:31000,对B要给A发送消息应该在192.168.1.1:30000。不幸的是,AB也许没有办法知道这些地址,因为S仅仅看到一个全局的公共地址,即155.99.25.11:62000 155.99.25.11:62001。即使AB有办法知道这些地址,仍然不能保证他们(这些地址)可用,因为ISP私有地址范围可能会和客户端私有地址范围冲突。此时,客户端没有选择,只能依赖由S端看到的全局的公网地址来进行P2P通讯,并依靠NAT X进行“回路解释”(loopback translation)

 

 

3.3.4. 一致端口绑定/Consistent port bindings

 

 

   这种穿孔技术有一个主要的警告:仅仅在双方NATscone NATs的时候,他才起作用,或者是非NAT 防火墙(non-NAT firewalls),只要那个UDP端口在使用,他就对特定的(私有IP, 私有UDP端口)(公共IP, 公共UDP端口)维护了一致的端口绑定。像对称NAT那样为每次会话分配一个新的公网端口的话,就会使得UDP应用程序不能重用已经建立的解释来与不同的外部主机通讯。由于cone NATs是很普遍的,所以UDP穿孔技术有相当广泛的应用。不过,有的NATs还是对称的,他们不支持这项技术。

 

 

3.4. UDP端口号预测

   上面讨论的UDP穿孔技术存在不同的情况,就是允许P2P会话建立在当前的一些对称NAT上。这种技术有时被称为"N+1"技术[BIDIR],并由Takeda [SYM-STUM]详细讨论了。这种方法通过分析NAT的行为来工作,并且试图猜测NAT将要为新会话分配的端口号。再次,我们来看下面的情况,ABserver S建立了UDP连接:

 

 

                           Server S

                        18.181.0.31:1234

                              |

                              |

              +----------------------+---------------------+

              |                               |

       Symmetric NAT A                Symmetric NAT B

   A-S 155.99.25.11:62000               B-S 138.76.29.7:31000

              |                               |

              |                               |

           Client A                          Client B

        10.0.0.1:1234                       10.1.1.3:1234

 

 

   NAT A 已经分配了一个UDP端口62000AS的会话,NAT B 分配了31000端口给BS的会话。通过SAB知道了彼此的公网地址和端口号。现在A138.76.29.7:31001发送UDP消息(注意,端口号加1)B同时给155.99.25.11:62001发送一个包。如果ABNAT都是顺序分配新会话的端口号,并且如果从A-SB-S初始会话还没有多少时间的话,那么一个AB的双向连接应该可以建立了。AB的消息使得NAT A打开了一个新的会话,A希望得到NAT A分配62001这个端口号,因为62001是刚才为A-S分配的端口号62000的下一个端口。类似的,BA的消息使得NAT B建立了一个新的会话,同样B希望得到31001这个端口号。如果双方都正确的猜到了各自NAT将要为新会话分配的端口号,如下图的双向连接就应该能够被建立起来:

 

 

 

 

 

 

                           Server S

                        18.181.0.31:1234

                              |

                              |

              +----------------------+---------------------+

              |                               |

            NAT A                          NAT B

   A-S 155.99.25.11:62000               B-S 138.76.29.7:31000

   A-B 155.99.25.11:62001               B-A 138.76.29.7:31001

              |                                |

              |                                |

           Client A                          Client B

        10.0.0.1:1234                      10.1.1.3:1234

 

 

   显然,有很多的原因可能导致这个小技巧失败。如果预测的端口号已经由一个不相关的会话使用了,那么NAT会就会跳过这个端口,连接就会失败。如果NAT有时候或者总是不是按顺序选择端口号,这个小技巧也会失败。如果NAT A(或者B)后面的其他主机在A(B)S建立连接之后,又在A(B)B(A)发送消息之前,又打开了一个新的和S的会话,那么这个不相关的客户端就会不经意的“窃取”到(我们)想要的端口号(的数据)。因此,这个技巧在双方的NAT负荷较重的时候,很可能不正常。

 

 

   由于实际中,实现这个小伎俩的P2P程序也需要在双方都是cone NATs,或者一方是cone NATs,另一方是对称NAT时,所以应用程序仍然要检测双方的NAT的类型,并且修改应用程序到相应的行为下工作,增加了算法的复杂性,和网络的脆弱性(译者:作者可能指信息会被其他人“窃取”这个问题)。最后,在多级NAT,且离客户端最近的NAT是对称NAT的时候,通过猜测端口的技术就没有机会成功了。由于种种这些原因,不推荐新的应用程序实现这个技术,这里做介绍,是出于历史和资料性的目的。

 

 

3.5. 同时打开TCP/Simultaneous TCP open

 

 

   有一个方法可以用来在双方都在中间设备(middleboxes)后面时建立P2P TCP连接。大多数的TCP会话始于一端发出一个SYN包,对端回应一个SYN-ACK包。然而,如果双方同时发给对方发送SYN包,再回复一个ACK包,就有可能建立一个合法的TCP会话。这个过程被称为“同时打开”(simultaneous open)

 

 

   如果中间设备收到一个外面的TCP SYN包,来试图初始化一个输入(incoming)TCP连接,中间设备通常会丢弃SYN包以拒绝这个连接,或者发送TCP RST(connection reset)包,然而,如果到达的SYN包源、目的地址和端口让中间设备以为他已经是活动的话,中间设备就会允许这个包通过。尤其是中间设备刚刚看到并且传送了一个有同样地址和端口的向外的SYN包,那么他会认为这个会话是活动的并放行这个incoming SYN。如果AB都能够正确的预测到中间设备将要分配给下一个向外的TCP连接的端口号,并且双方及时的向对方发出一个TCP连接,以便客户端的向外的SYN包在对方的SYN到达自己的中间设备之前穿过自己的中间设备,这样的话一个P2PTCP连接就建立起来了。

 

 

   不幸的是,这个小技术也许很脆弱且时间比上面提到的UDP端口预测技术严格。首先,除非双方的中间设备仅是防火墙或者由cone NAT实现,否则所有的事情---预测每一边NAT分配新会话的端口,都可能是错的。此外,如果一端的SYN包到达对方的中间设备太快,那么对方的中间设备就会拒绝SYN包并且发出RST包,致使本地的中间设备反过来关闭了新的会话且未来使用这个端口重传SYN会失败。最后,即使对“同时打开”的支持是TCP规范强制性的,在某些OS中也没有正确的实现。出于这些原因,同样这里提到的这个小技术也是出于历史的原因,应用程序不推荐使用。应用程序如果要求通过现有的NATs建立有效的,直接的P2P连接应该使用UDP

 

 

 

 

4. 应用程序设计指南/Application design guidelines

 

 

 

4.1. 有中间设备该怎么做/What works with P2P middleboxes

 

 

  由于UDP穿孔技术是为在NATs后面的主机建立P2P连接的现存的最有效的方法,并且他使用于现在大多数的NATs,如果应用程序需要建立有效的P2P连接,推荐使用这个技术。但是,也要准备好不能建立P2P连接的时候可以使用中继。

 

 

4.2. 双方在同一个NAT后面/Peers behind the same NAT

 

 

  实际情况下,有很多的用户不是有两个IP地址,而是三个或者更多。这些情况下,很难或者根本不可能分辨出那一个地址发向了注册服务器信息。这种情况下,应用程序应该送出他的所有地址(译注:在Windows中,即使程序强制使用了MSG_DONTROUTE,winsock也会悄悄的忽略这个选项,并总是通过路由表决定发出数据的适当的接口《windows网络编程技术》第一版 P210)

 

 

4.3. 对端的发现/Peer discovery

 

  应用程序发包到许多地址来发现对一个特定的端来说哪一个是最佳的,这也许会在网络上产生可观的垃圾,当对端可能不适当的为内网选用了一个可路由的地址(11.0.1.1,他是为DOD保留的)。因此,应用程序发出这种探测问候包时应当小心。

 

 

4.4. TCP P2P 应用程序

  应用程序开发人员广泛使用的sockets API被设计用来开发C/S应用程序。在他的最初形式中,一个socket仅仅可以绑定一个TCP或者UDP端口。应用程序不能不允许有多个socket绑定同一个TCP or UDP端口,来同时发出和多个外部节点的会话,或者使用一个socket在一个端口上listen,其他socket发起向外的连接会话。

 

 

  然而,上面的这种single-socket-to-port绑定的限制对UDP不是问题,因为UDP是基于数据报的协议。UDP程序设计者可以使用recvfrom()sendto()调用来用一个socket发送和接收到多个客户端的数据。

 

 

  TCP而言,就行不通了。对TCP,输入和输出对应一个单独的socketLinux socket APISO_REUSEADD选项来帮助解决这个问题。在FreeBSDNetBSD上,这个选项不行;但可以用SetReuseAddress这个BSD函数(Linux没有这个且不在Single Unix Standard)Win32 API也提供了一个等价的SetReuseAddress调用。使用上面提到的选项,应用程序可以使得一个TCP 端口供多个Socket重用。也就是说,打开2TCP socket绑定到一个端口上,一个用来listen,另一个用来connect

 

4.5. 使用midcom协议/Use of midcom protocol

 

 

  如果应用程序知道他要穿越的中间设备和实现这些中间设备的协议,应用程序就能够使用midcom协议来简化通过中间设备的过程。(译注:这里的midcom我想指的是NAT的端口映射规则、会话映射保留时间、incoming TCP packages 的处理等等)

 

 

  比如,P2P应用程序要求NAT中间设备保留端口绑定。如果中间设备支持midcomP2P应用程序就可以控制端口绑定(或者地址绑定)参数,如存活时间,最大的idle时间,方向性---以使应用程序既可以连接又可以接收外部节点;不需要周期性的发送数据以使端口绑定存活。当应用程序不再需要绑定时,应用程序可以简单的使用midcom协议的卸载命令。

 

 

5. NAT 设计指南/NAT Design Guidelines

 

 

 

   由于NAT影响P2P应用程序,本节讨论他们的设计。

 

 

5.1. 不赞成使用对称NATs/Deprecate the use of symmetric NATs

 

 

   对称NATs随着C/S应用程序如web浏览器而获得普及,他仅需要发起向外的连接。然而,最近P2P应用程序如即时消息和音频会议有着广泛应用。对称NATs不支持保留终端身份的特性也不适合P2P应用程序。为了支持P2P应用程序,不赞成使用对称NATs

 

 

   P2P中间设备必须为UDP通讯实现cone NAT的功能,允许通过UDP穿孔技术建立强健的P2P连接。理想情况下,P2P中间设备应该允许使用TCPUDP建立P2P连接。

 

 

5.2. 为对称-NAT设备添加cone-NAT支持

 

 

   对称NAT设备扩展支持P2P应用程序的一个方法是划分可分配端口号的名字空间,保留一定比例的端口给one-to-one的会话和一些不同的端口给one-to-many会话。

 

 

   此外,一个NAT设备可被显示的配置支持应用程序和主机所需要的P2P特性,以便NAT设备可自动的从合适的端口号码块中分配出P2P端口。

 

 

5.3. UDP端口维护一致的端口绑定/Maintain consistent port bindings for UDP ports

 

 

   本篇文档对NAT设计者的最基本和最重要的建议是,只要有一个活动的会话存在于给定的端口上,NAT就要为给定的 (内部IP,内部PORT)(外部IP,外部PORT)对维护一致的稳定的端口绑定。NAT可以检查每一个输入包的源、目的端口和地址过滤每一个输入的会话通讯。当一个私有网络中的节点发起一个向外的连接时,使用同一个源IPUDP端口来解释这个UDP会话,NAT应该确保当(某个内部主机)存在会话时,(这个内部主机的)新的UDP会话被指定在同一个公共IPUDP端口号上。

 

 

5.3.1. 保留端口号/Preserving port numbers

 

 

   当建立新的UDP会话时,如果这个端口可用的话,有些NATs会尝试分配和私有端口号(内网主机的端口)一致的公共端口(NAT上新分配的端口)。比如,A从内网10.0.0.1:1234发出一个向外的UDP会话,那么NAT就会在自己分配端口时试图使用1234这个端口。这项特性对一些想仅使用某个特定的UDP端口来会话的UDP应用程序而言会很有用,但是不推荐应用程序依赖这个特性,因为仅当内网至多一个主机使用这个端口时,NAT才会保留这个端口可用。

 

 

   此外,NAT不应该在一个新会话中保留端口,如果这样做了,会和维护公共终端--私有终端地址绑定的一致性冲突。例如,假定内网主机A在端口1234server S建立了会话,NAT A为这个会话分配了62000端口,因为此时NAT上的1234端口不可用。现在假定接下来NAT1234端口可用了,而AS的会话仍然是活动的,内网主机A又以1234端口初始一个和其他外部节点B的连接。这种情况下,由于端口绑定已经建立的是A1234端口和NAT62000的公网端口,这个端口绑定应该受维护,并且新的会话也应该使用62000端口作为公网端口和内网主机A1234端口一致。NAT不能仅仅因为1234端口可用,就分配1234端口给这个新的会话:这个行为不可能使得应用程序以任何方式获益,因为应用程序已经用一个已经分配的端口运行了,并且,他还会妨碍应用程序使用UDP穿孔技术和这个内网主机建立P2P连接的尝试。

 

 

5.4. 维护TCP端口的一致性绑定/Maintaining consistent port bindings for TCP ports

 

 

   为了和UDP解释相一致,cone NAT实现者也应该为TCP维护一致的(ip, port)在公网和私网的映射,就象上面描述的UDP的情况一样。维护TCP终端端口绑定的一致性会增加NATP2P TCP应用程序的兼容性,这些应用程序从同一个源端口初始发起多个TCP连接。

  

5.5. P2P应用程序大的超时时间/Large timeout for P2P applications

 

 

   我们推荐中间设备实现者为P2P应用程序使用最小的超时时间,比如5分钟,配置绑定端口的idle-timeout。中间设备的实现者通常会使用更小的一个,就像他们现在做的。但是,更小的超时时间可能会有问题。考虑一下,一个P2P应用程序有16个对端。他每隔10秒发送激活包(keepalive packets)来避免NAT超时就会淹没网络。之所以这样是因为他会给中间设备发送5次激活包,以免激活包在网络中丢失。

  

5.6. 支持回路解释/Support loopback translation

 

 

   我们强烈建议中间设备实现者支持回路解释功能,允许同一个中间设备后面的主机通过他们自己的公共的中间设备彼此通讯。在多级NAT的情况下,大容量的NATs被放置在第一级时,支持回路解释尤为重要。象3.3.3节描述的那样,在第一级NAT相同,但是第二级的NAT不相同时,除非第一级的NAT也支持回路解释,否则无法使用UDP穿孔技术彼此通讯,即使所有的中间设备保留终端的身份。

  

6. 安全考虑/Security Considerations

 

 

 

   本篇接下来的建议使得对应用程序或中间设备不会产生新的天生的安全问题。然而,如果这里描述的技术没有受到足够的重视的话,新的安全风险也许会有。本节描述应用程序穿越中间设备建立P2P通讯时可能不经意的产生的安全问题和中间设备P2P友好时的隐式的安全策略。

 

 

6.1. IP地址别名/IP address aliasing

 

 

   P2P应用程序必须采用适当的授权机制来保护他们的P2P连接不受其他P2P连接的意外骚扰,也为了防止恶意的拦截连接或者DOS(denial-of-service)攻击。有效的NAT友好的P2P应用程序必须和多个不同IP地址域进行交互,但是一般不会注意到他们的确切拓扑或者这些地址域的管理策略。而通过UDP穿孔技术建立P2P通讯时,应用程序发出的包可能会经常到达整个网络而不只是想要的那个主机。

 

 

   例如,多层NAT设备提供DHCP服务,默认配置为分发给本地IP一个特定的地址范围。比如,一个特定的NAT设备,默认情况,把192.168.1.100起始的地址分发。许多家庭私有使用NAT的网络会有一个在那个地址上的主机,而这个地址也可能是192.168.1.101,如果一个主机AUDP穿孔技术在一个私有网络192.168.1.101中试图和另一个私有网络中的主机B 192.168.1.100建立P2P连接,那么,作为建立连接的过程的一部分,A会发送给192.168.1.100本地网络一个寻找包,B也会给本地网络发送一个到192.168.1.101的寻找包。显然,这些寻找包不能到达想要的目的主机,因为两个主机在两个不同的私网中,但是他们很可能到达各自网络中使用这个标准UDP端口的应用程序中,造成潜在的混乱。尤其是如果这个应用程序也在其他的机器上运行且没有适当的授权信息时。

 

 

   这个由于别名引起的风险,即使在没有恶意攻击的情况下也可能有。如果一个终端,比如A,确实是恶意的,那么没有适当授权的情况下,攻击者就可以使得主机B以一种我们不期望的方式连接到和攻击者(或者假想的攻击者)同一个私网地址的其他主机。由于两个终端主机AB应该知道彼此是通过公网服务器S的,SB都没有办法确认A给出的私有地址,所以,P2P应用程序必须认为他们发现的任何IP地址都是可疑的,直到他们成功的建立了授权的双向通讯。

 

 

6.2. DOS攻击/Denial-of-service attacks

 

 

   P2P应用程序和支持该应用程序的公共servers必须保护自己不受DOS攻击,并且确保他们不能被攻击者用来安装DOS攻击其他目标。要保护他们自身,P2P应用程序和服务器必须在合法的双向连接建立以前避免需要大量的本地处理或存储资源。要避免被作为一个DOS攻击的工具,P2P应用程序和服务器在他们和预想的目标建立合法的P2P连接之前,必须最小化发送给新发现的目的IP的通信量的数量和速率。

 

 

   例如,注册到公共server上的P2P应用程序可以告知自己的私有IP,或者多个IP地址。一个连接好的主机或者能够吸引大量P2P连接的(例如,通过提供流行的内容服务)主机群可安装一个对目标主机CDOS攻击,仅仅通过在向服务器注册时,把CIP地址包含到自己的IP地址列表中就行了。服务器没有办法确认这些IP地址,因为他们也可能是合法的用来建立本地网络通讯的私有IP地址。

   P2P应用程序协议必须设计成对这些未经校验的IP地址大小和速率受限的通讯,以避免导致这种集中效用带来的潜在伤害。

 

 

6.3. 内部发起的攻击/Man-in-the-middle attacks

 

 

   任何在P2P客户端和集中服务器路径之间的网络设备,都可能安装伪装成NAT的各种man-in-the-middle攻击。比如,假定A要向服务器注册,但是一个正在监听的攻击者能够观察到这种注册请求。攻击者就可以通过发送和客户请求一致的请求(除了修改源IP地址,比如改成攻击者自己的)来淹没服务器。如果攻击者能够说服服务器以攻击者的身份注册,那么他就成为未来这个通讯线路中代替原来客户端的一个活动的组成部分了,即使攻击者原来仅仅是嗅探到客户到服务器的路径。

 

 

   客户端不能向服务器授权自己的源IP来保护他自己免受这种攻击,因为为了成为一个友好的NAT,应用程序必须允许NATs可以静静的修改源IP地址。这对NAT模型就出现了一个天生的安全缺陷。对这种攻击唯一的防御手段是用适当高的级别来鉴定和加密实际的   通信内容,这样攻击者就不能利用他的位置(译注:指内网可以方便的监听)。然而,即使所有应用层的通讯都被授权和加密处理,这种攻击仍然可用通讯工具来分析客户端正在和谁进行通讯。

 

 

6.4. 对中间设备安全的影响/Impact on middlebox security

   让中间设备保留终端身份不会削弱中间设备的安全。比如,一个端口受限的cone NAT,在允许输入和输出通讯包通过中间设备的策略上,先天就不会比对称NAT更加“混杂”(promiscuous)。只要向外的UDP会话可行,并且中间设备对内部和外部的UDP端口维护绑定的一致性,那么,中间设备就可以过滤出从内部发起的不匹配的任何UDP输入包。同样,当维护端口绑定一致时,严格的过滤输入包同样使得中间设备是P2P友好的,而不会妥协拒绝不请自来包的原则。

 

 

   维护端口绑定一致性可导致对来自中间设备通讯的可预测性增加,这种预测性的增加是通过揭示不同UDP会话直接的关系,进而这个范围里面应用程序的行为而获得。这种可预测性对攻击者在利用其他网络或者应用程序级别的脆弱性可能很有用。然而,如果特殊情况下的安全要求非常的严格,要考虑的细致入微,那么,首先中间设备就不应该被配置成许可不严格的向外UDP通讯。这些中间设备应该仅仅允许从特定的应用程序在特定的端口,或者通过一个严格控制的应用层的网关进行通讯。这种情况下,就没有希望穿过中间设备建立一般的、透明的P2P通讯(或者透明的c/s连接);中间设备要么实现适当的具体应用程序的行为,要么整个就不允许通讯。

 

 

7. 致谢/Acknowledgments

 

 

   The authors wish to thank Henrik, Dave, and Christian Huitema for their valuable feedback.

 

 

8. References

 

 

8.1. Normative references

 [BIDIR]    Peer-to-Peer Working Group, NAT/Firewall Working Committee, "Bidirectional Peer-to-Peer Communication with Interposing Firewalls and NATs", August 2001. http://www.peer-to-peerwg.org/tech/nat/

 

 

[KEGEL] Dan Kegel, "NAT and Peer-to-Peer Networking", July 1999. http://www.alumni.caltech.edu/~dank/peer-nat.html

 

 

[MIDCOM]    P. Srisuresh, J. Kuthan, J. Rosenberg, A. Molitor, and A. Rayhan, "Middlebox communication architecture and framework", RFC 3303, August 2002.

 

 

[NAT-APPL]   D. Senie, "Network Address Translator (NAT)-Friendly Application Design Guidelines", RFC 3235, January 2002.

 

 

[NAT-PROT]   M. Holdrege and P. Srisuresh, "Protocol Complications with the IP Network Address Translator", RFC 3027,  January 2001.

 

 

[NAT-PT]     G. Tsirtsis and P. Srisuresh, "Network Address Translation - Protocol Translation (NAT-PT)", RFC 2766, February 2000.

 

 

[NAT-TERM]  P. Srisuresh and M. Holdrege, "IP Network Address Translator (NAT) Terminology and Considerations", RFC 2663, August 1999.

 

 

[NAT-TRAD]   P. Srisuresh and K. Egevang, "Traditional IP Network Address Translator (Traditional NAT)", RFC 3022, January 2001.

 

 

[STUN]       J. Rosenberg, J. Weinberger, C. Huitema, and R. Mahy, "STUN - Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs)", RFC 3489, March 2003.

 

 

8.2. Informational references

 

 

[ICE]      J. Rosenberg, "Interactive Connectivity Establishment (ICE): A Methodology for Network Address Translator (NAT) Traversal for the Session Initiation Protocol (SIP)", draft-rosenberg-sipping-ice-00 (Work In Progress), February 2003.

 

 

[RSIP]     M. Borella, J. Lo, D. Grabelsky, and G. Montenegro, "Realm Specific IP: Framework", RFC 3102, October 2001.

 

 

[SOCKS]    M. Leech, M. Ganis, Y. Lee, R. Kuris, D. Koblas, and L. Jones, "SOCKS Protocol Version 5", RFC 1928, March 1996.

 

 

[SYM-STUN] Y. Takeda, "Symmetric NAT Traversal using STUN", draft-takeda-symmetric-nat-traversal-00.txt (Work In Progress), June 2003.

 [TCP]      "Transmission Control Protocol", RFC 793, September 1981.

 [TEREDO]   C. Huitema, "Teredo: Tunneling IPv6 over UDP through NATs", draft-ietf-ngtrans-shipworm-08.txt (Work In Progress), September 2002.

 

 

[TURN]     J. Rosenberg, J. Weinberger, R. Mahy, and C. Huitema, "Traversal Using Relay NAT (TURN)", draft-rosenberg-midcom-turn-01 (Work In Progress), March 2003.

 

 

[UPNP]     UPnP Forum, "Internet Gateway Device (IGD) Standardized Device Control Protocol V 1.0", November 2001. http://www.upnp.org/standardizeddcps/igd.asp

 

 

9. Author's Address

 

 

   Bryan Ford

   Laboratory for Computer Science

   Massachusetts Institute of Technology

   77 Massachusetts Ave.

   Cambridge, MA 02139

   Phone: (617) 253-5261

   E-mail: baford@mit.edu

   Web: http://www.brynosaurus.com/

 

 

   Pyda Srisuresh

   Caymas Systems, Inc.

   11799-A North McDowell Blvd.

   Petaluma, CA 94954

   Phone: (707) 283-5063

   E-mail: srisuresh@yahoo.com

 

 

   Dan Kegel

   Kegel.com

   901 S. Sycamore Ave.

   Los Angeles, CA 90036

   Phone: 323 931-6717   

   Email: dank@kegel.com

   Web: http://www.kegel.com/

 

 

 

 

Full Copyright Statement

 

 

   Copyright (C) The Internet Society (2003).  All Rights Reserved.

 

 

   This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works.  However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English.

 

 

   The limited permissions granted above are perpetual and will not be revoked by the Internet Society or its successors or assigns.

 

 

   This document and the information contained herein is provided on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

 

 

------ < END > ------

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值