TCP点对点穿透探索

点对点穿透是穿透什么

点对点穿透,需要实现的是对NAT的穿透。想实现NAT的穿透,当然要先了解NAT到底是什么,以及NAT是用来干什么的。
NAT全称Network Address Translation,意思是网络地址转换,在1994年提出。它可以对不同的IP及端口进行映射,将一个网络地址转换为另一个。NAT的主要用途,大家可以看路由器。路由器具有一个WAN口及多个LAN口;WAN口对外,连接因特网,拥有公网IP;LAN口对内,构建本地网络,分配的是私网IP。当处于LAN网下的本地主机想要访问因特网的时候,路由器就会通过NAT技术,将LAN 口的私网IP映射到WAN口的公网IP,实现网络地址的转换,这样本地主机就可以访问因特网了。
NAT技术的出现,有效减缓了IPv4时代可用IP地址枯竭的问题。至于为何可以缓解IP地址枯竭,我们依旧可以参照路由器来加以理解。路由器的LAN网下扩展了多台本地主机,这些本地主机需要不同的IP地址加以区分。如果它们都是接在英特网下,那么每台主机都需要消耗一个公网IP,但是通过路由器的NAT服务,这些本地主机可以先分配不同的私网IP,然后在需要连接到英特网的时候映射到公网IP的不同端口上完成对因特网的访问,从而节省IP地址的消耗。至于私网IP地址,由于NAT的存在,本地网络与英特网处于隔离状态,不必担心与其他网络产生冲突。
上面是对NAT的一些简单的介绍,以及NAT工作方式的简单描述,如果觉得难以理解,可以自行查阅更多资料。

举个例子,假如路由器WAN口获取到的公网IP是55.66.77.88(随便写的),LAN网下某台本地主机获取到的私网IP是192.168.0.100(路由器多是192.168.0.0网段)。现在本地主机想要向英特网发起连接,它通过自己的10000端口发起了连接,路由器知道有本地主机向英特网发起连接,便会分配一个WAN口的可用端口做映射,比如分配到的是5000端口。这时,192.168.0.10010000端口便和55.66.77.885000端口产生了映射关系。55.66.77.885000端口收到的网络包便会转发到192.168.0.10010000端口,192.168.0.10010000端口收到的网络包也会转发到55.66.77.885000端口。当然,由于是网络地址转换,这途中还会有拆包,重新装包的过程,不做详细说明。

大致知道NAT怎么工作的之后,接下来了解为什么要穿透NAT。
按照上面的描述,NAT的工作需要LAN网下的本地设备主动发起网络连接,然后NAT服务才会将这个连接映射到WAN口的公网IP完成转换。也就是说,如果本地主机没有主动发起连接,那么这个映射就不会存在,那么公网上的机器就无法访问到私网上的机器。也就是说,只能是私网机器主动连接公网机器,而不能是公网机器主动连接私网机器。而为了实现公网机器主动连接私网机器,我们就需要穿透NAT,这就是NAT穿透的由来。

目前比较好实现NAT穿透的方式是采用UDP连接对NAT进行打洞,然后完成连接。何为打洞呢?就是为了使NAT产生一个可用的映射。具体步骤就是在私网机器上用UDP向某台公网机器发起连接,使得NAT产生一个可以使用的映射(洞)。然后通过这个映射(洞),就可以穿透NAT。
至于为什么要用UDP,这是由于UDP的某些特性。UDP通信需要先绑定本地机器的端口,完成后就可以从这个端口收发数据,至于从哪里收,发到哪里,可以在收发数据的时候再决定,这也就意味着我可以用这一个端口同时和多个对象通信,只要我收发数据的时候指定不同的对象即可。当本地机器用UDP向英特网上的某个服务器发送数据的时候,这个映射不但能用来和这个服务器进行数据交互,也能用来接收其他主机发来的数据。NAT穿透就是本地主机向公网上的某台服务器发送数据,这时服务器就可以获得NAT对这台主机的映射,在之前举得例子中就是55.66.77.88:5000这个地址。由于NAT会将55.66.77.88:5000收到的数据转发至本地主机,所以公网上的其他机器可以从服务器获取到55.66.77.88:5000这个网络地址,然后通过这个网络地址向私网下的机器发出数据。
而至于为什么不用TCP,也是由于TCP的某些特性。TCP通信的步骤与UDP不同,它需要先在两个对象之间建立一个专用通道,再用这个通道收发数据。也就是说外人无法插手。这样一来,虽然其他机器可以通过服务器获取到NAT的映射对象,也没办法利用它向私网下的机器发出数据。
关于TCP与UDP的更多细节,请参考SOCKET编程。

TCP实现点对点穿透的探索

为了尝试使用TCP实现点对点穿透,需要现对TCP做更多的了解。我之前有详细查过TCP连接中的各种状态变化,做了简单的整理,可以做个参考:TCP连接状态变化
既然TCP在连接过程中其他人不能插手,但是等它连接结束之后呢?NAT对TCP连接的端口映射在连接结束后就立马销毁了吗?接着深入,发现NAT存在一个老化机制。接下来看看老化是什么意思。NAT生成某个映射后,会将这个映射保存下来,但是即使端口号非常多,它也不是无限的,而既然端口号是有限资源,那么就不能保证映射表的无限扩充。为了合理利用资源,当某个映射一段时间内没有发生数据交互,NAT就会认为这个映射已经没有人使用了,就会将这个映射销毁,回收端口号。这个时间,就叫做老化时间。也就是说,老化是一种映射的回收机制。
TCP连接状态变化中可以知道,TCP在断开连接后会有一段时间的保护期,不让这个端口进行下一次连接,这个时间是2*MSL,MSL在协议中的建议值为2分钟,实际应用中常用是30秒,1分钟和2分钟,也就是说这个时间很有可能是一分钟甚至更久。那老化时间有多久有多久呢,在老化时间控制中有提到,TCP的默认老化时间是86400秒,TCP-SYN和TCP-FIN的默认老化时间是60秒。这样说来,按照一般情况等保护期结束的时候,NAT的映射也到期了。
但是没有关系,SOCKET编程中允许有一些特殊的选项,其中有一个叫SO_REUSEADDR的选项。

以下文字引用自setsockopt中参数之SO_REUSEADDR的意义

setsockopt中参数之SO_REUSEADDR的意义

1、一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。TCP,先调用close()的一方会进入TIME_WAIT状态

2、SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR提供如下四个功能:
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。
SO_REUSEPORT选项有如下语义:
此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。
如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。
使用这两个套接口选项的建议:
在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
   (const void *)&nOptval , sizeof(int)) < 0) ...

关于SO_REUSEADDR的特性,网上介绍很多,这里贴出几个链接:
SO_REUSEADDR例解
SO_REUSEADDR 套接字选项应用实例

有了SO_REUSEADDR就好办了,在刚断开连接的时候NAT的映射还没有被老化,而由于SO_REUSEADDR套接字选项的关系,也可以立马进行下一次连接。也就是说只要我们在NAT服务设置的老化时间内重新建立好连接,那么这个映射就可以继续使用。

从原理上来说应该是存在可行性的,如果有偏颇,忘指正。后续会尝试搭建环境,写程序做个试验。

在这里写一下之前做的试验:连接关闭之后NAT的端口映射直接失效,根本无法建立下一次连接,知识储备还差点儿,太想当然了。

TCP点对点穿透探索--失败的更多相关文章

  1. TCP点对点转发的实现与原理(nodejs)

    Nagent Nagent是TCP点对点转发实现,名称来源于Nat与Agent的组合.类似frp项目,可以在局域网与互联网提供桥梁. 前提是你要有一台流量服务器并且有一个公网IP.如果没有,也可以找服 ...

  2. tcp 在调用connect失败后要不要重新socket

    tcp 在调用connect失败后要不要重新socket http://blog.csdn.net/occupy8/article/details/48253251

  3. 利用ZoomPipeline迅速实现基于线程池的全异步TCP点对点代理

    在博文<一种基于Qt的可伸缩的全异步C/S架构服务器实现>中提到的高度模块化的类可以进行任意拆解,实现非常灵活的功能.今天,我们来看一看一个公司局域网访问英特网云服务器的点对点代理例子.代 ...

  4. .Net TCP探索(一)——TCP服务端开发(同时监听多个客户端请求)

        最近在园子里看了大神写的(面试官,不要再问我三次握手和四次挥手),忍不住写段程序来测试一番.     在网上找了很多例子,大多只实现了TCP点对点通讯,但实际应用中,一个服务器端口往往要监听多 ...

  5. 使用TCP协议的NAT穿透技术

    一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基本上没有人会告诉你如何使用TCP协议去穿透(甚至有的人会直接告诉你TCP协议是无法实现穿透的).但是,众所周知的是,UDP是一个无连 ...

  6. 使用TCP协议的NAT穿透技术(转)

    其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之. 一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基 ...

  7. 使用TCP协议的NAT穿透技术 (转载)

    其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之. 一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基 ...

  8. NAT穿透的详解及分析

    一.什么是NAT?为什么要使用NAT?NAT是将私有地址转换为合法IP地址的技术,通俗的讲就是将内网与内网通信时怎么将内网私有IP地址转换为可在网络中传播的合法IP地址.NAT的出现完美地解决了lP地 ...

  9. NAT穿透的详细讲解及分析.RP

    原创出处:https://bbs.pediy.com/thread-131961.htm 转载来源: https://blog.csdn.net/g_brightboy/article/details ...

随机推荐

  1. centOS解决乱码问题

    问题描述:输入javac出现乱码,部分字符不能显示解决方法 echo 'export LANG=en_US.UTF-8' >> ~/.bashrc

  2. erlang程序优化点的总结

    注意,这里只是给出一个总结,具体性能需要根据实际环境和需要来确定 霸爷指出,新的erlang虚拟机有很多调优启动参数,今后现在这个方面深挖一下. 1. 进程标志设置: 消息和binary内存:erla ...

  3. 具体解释TCP协议的服务特点以及连接建立与终止的过程(俗称三次握手四次挥手)

    转载请附本文的链接地址:http://blog.csdn.net/sahadev_/article/details/50780825 ,谢谢. tcp/ip技术经常会在我们面试的时候出现,非常多公司也 ...

  4. 内存MCE错误导致暴力扩充messages日志 以及chattr记录

    由于放假,好久没登过服务器,今天登上服务器查看日志意外发现:/var/log/messages文件竟然被撑到20多个G!!!赶紧查看是什么情况,首先,20多个G的文件根本无法查看,因此,我想到了spl ...

  5. Ajax的跨域问题

    •跨域问题概述 •出于安全考虑,浏览器不允许ajax跨域获取数据 •可以通过script的src加载js的方式传递数据 fn({"a":"1","b& ...

  6. 九度OJ 1176:树查找 (完全二叉树)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:5209 解决:2193 题目描述: 有一棵树,输出某一深度的所有节点,有则输出这些节点,无则输出EMPTY.该树是完全二叉树. 输入: 输入 ...

  7. php总结8——mysql函数库、增删改

    8.1 mysql函数库 php的函数   .php中用来操作mysql函数库的函数 常用函数 mysql_connect("主机名称/ip","用户名",&q ...

  8. 使用nginx+nginx-rtmp-module+ffmpeg搭建流媒体服务器

    参考: 1,使用nginx+nginx-rtmp-module+ffmpeg搭建流媒体服务器笔记(一)http://blog.csdn.net/xdwyyan/article/details/4319 ...

  9. 关于button的自动刷新

    今天在开发中遇到了ajax传数据到后台,处理结果正常,返回的resultMap是一个Map<String,Object>类型,但是返回时显示'Fail to load response d ...

  10. 安装pymysqlpool并使用(待补充)

    pip3 install PyMysqlPool 第一个错,提示没有装c++ 14.0,下载安装报下一个错 error: Setup script exited with error: Microso ...