TCP
服务端的端口 复用为 客户端的端口 ( 服务端发送无歧义, 服务端接收无歧义, 客户端发送无歧义, 客户端接收无歧义)
服务端的端口 复用为 服务端的端口 正常现象
客户端的端口 复用为 服务端的端口 (客户端发送无歧义, 客户端发送无歧义,服务端发送无歧义, )
客户端的端口 复用为 客户端的端口 无歧义
TCP端口复用要求地址不能相同(采用多网卡技术,可以是虚拟网卡或硬件网卡混搭,或者用IP alias技术)。
NAT记录的是 内网源客户端IP和内网源客户端端口,所以NAT收到的TCP包里的路由器公网IP只会被重新设置为内网源客户端IP,所以复用的新套接字(新套接字使用了新的内网IP)是无法收到这个消息的,所以外网的TCP客户端的握手都无法被转达了,那就根本不可能实现通信了。但是端口仍然被复用了,只不过NAT网关无法起到映射功能而已。(除非NAT能够辨别出,复用后的内网主机身份,这个需要NAT设备增强)
每次复用都会杀死复用前的套接字。
如果复用前的套接字是客户端套接字,那么这个套接字也是设置为SO_REUSEADDR
服务端的套接字通常被设置为SO_REUSEADDR,这个是否安全呢?如果不设置的话,套Listen阶段被杀死,端口会不会泄露(脱离操作系统管理)呢?答:我觉得不安全,低权限的进程会在高权限的进程服务器以外死亡时获得高权限的套接字,但是没办法,否则会造成泄露。
sock在什么情况下会TIME_WAIT?
为什么TCP无法做到允许地址一样的端口复用?防止不安全的SO_REUSEADDR问题变得更加恶化(高权限的进程套接字可能会随意被挤掉)。
在TCP中设置为SO_REUSEADDR的套接字,仅能起到【当套接字未被释放(仍然存活于内核的套接字文件缓存中)时,最后一个拥有能释放套接字权利的进程被杀死了(这种套接字就跟孤儿一样)】,这次SO_REUSEADDR的套接字能被新的套接字通过端口复用(IP不一样)将它杀死。(要实现穿洞需要增强NAT的功能)
UDP
在绑定的时候是半个套接字。只有在收发时创建临时套接字。
UDP 在多播模式下端口复用允许IP地址也一样,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。
UDP实现NAT穿洞之后,复用前的套接字会失效(可以用来杀死孤儿套接字,也可以用来实现内网穿透)。
NAT究竟是怎样的技术?
sock在什么情况下会TIME_WAIT?
BSD系统引入了SO_REUSEPORT参数,该参数用于区分端口重用还是地址重用,在这样的系统里面,上述所有的参数必须都设置才行。
结论:TCP不能做NAT穿洞,UDP可以。TCP要实现穿洞只能依赖于隧道技术,即通过VPN转发 或 只用超级NAT功能。
超级NAT能够辨识同一主机上的不同IP,方法就是在第一个访问NAT的时候颁发一枚身份证,之后不管什么IP访问NAT都必须带上这个身份证(用于标识主机的身份,达到识别主机唯一性的能力)。或者让内网服务器套接字具有发送syn的功能,先往想要链接自己服务器的Client发送一个syn(调用的异步connect()就会发送syn了).
具体实现看下面
原文:http://www.cnblogs.com/liu-q/p/4158213.html
现在来看更加实际的一种情景,A与B分别位于不同的NAT设备后面,并且假定端口号是TCP协议的端口号,而不是UDP的端口号。客户端向彼此公网endpoint发起连接的操作,会使得各自的NAT设备打开新的"洞"允许A与B的TCP数据通过。如果NAT设备支持TCP"打洞"操作的话,一个在客户端之间的基于TCP协议的流通道就会自动建立起来。如果A向B发送的第一个SYN包发到了B的NAT设备,而B在此前没有向A发送SYN包,B的NAT设备会丢弃这个包,这会引起A的"连接失败"或"无法连接"问题。而此时,由于A已经向B发送过SYN包,B发往A的SYN包将被看作是由A发往B的包的回应的一部分,所以B发往A的SYN包会顺利地通过A的NAT设备,到达A,从而建立起A与B的p2p连接。
三. 从应用程序的角度来看TCP"打洞"
从应用程序的角度来看,在进行TCP"打洞"的时候都发生了什么呢?假定A首先向B发出SYN包,该包发往B的公网endpoint,并且被B的NAT设备丢弃,但是B发往A的公网endpoint的SYN包则通过A的NAT到达了A,然后,会发生以下的两种结果中的一种,具体是哪一种取决于操作系统对TCP协议的实现:
(1)A的TCP事先会发现收到的SYN包就是其发起连接并希望联入的B的SYN包,通俗一点来说就是"说曹操,曹操到"的意思,本来A要去找B,结果B自己找上门来了。A的TCP协议栈因此会把B做为A向B发起连接connect的一部分,并认为连接已经成功。程序A调用的异步connect()函数将成功返回,A的listen()等待从外部联入的函数将没有任何反映。此时,B联入A的操作在A程序的内部被理解为A联入B连接成功,并且A开始使用这个连接与B开始p2p通信。
由于收到的SYN包中不包含A需要的ACK数据,因此,A的TCP将用SYN-ACK包回应B的公网endpoint,并且将使用先前A发向B的SYN包一样的序列号。一旦B的TCP收到由A发来的SYN-ACK包,则把自己的ACK包发给A,然后两端建立起TCP连接。简单的说,第一种,就是即使A发往B的SYN包被B的NAT丢弃了,但是由于B发往A的包到达了A。结果是,A认为自己连接成功了,B也认为自己连接成功了,不管是谁成功了,总之连接是已经建立起来了。
(2)另外一种结果是,A的TCP实现没有像(1)中所讲的那么"智能",它没有发现现在联入的B就是自己希望联入的。就好比在机场接人,明明遇到了自己想要接的人却不认识,误认为是其它的人,安排别人给接走了,后来才知道是自己错过了机会,但是无论如何,人已经接到了任务已经完成了。然后,A通过常规的listen()函数和accept()函数得到与B的连接,而由A发起的向B的公网endpoint的连接会以失败告终。尽管A向B的连接失败,A仍然得到了B发起的向A的连接,等效于A与B之间已经联通,不管中间过程如何,A与B已经连接起来了,结果是A和B的基于TCP协议的p2p连接已经建立起来了。
第一种结果适用于基于BSD的操作系统对于TCP的实现,而第二种结果更加普遍一些,多数linux和windows系统都会按照第二种结果来处理。
对于tcp打洞这回事,我感觉就是有点像忽悠,忽悠来忽悠去,忽悠着忽悠着就忽悠上了,呵呵,人生何尝不是?
LD:谜底是什么呢?最终还是得看NAT算法了或者PCP协议
主流的穿洞方法是: UPnP技术和隧道代理技术