• TCP点对点穿透探索--失败


    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的端口映射直接失效,根本无法建立下一次连接,知识储备还差点儿,太想当然了。

  • 相关阅读:
    js 格式化时间
    javascript Base64 加密解密方法
    为什么给元素添加了z-index却没有效果
    浮动 与 清除浮动
    Vue 介绍 以及 学习总结(这里不详细展开)
    redux 的基础用法
    c# 几种深拷贝方式的比较
    C#四种深拷贝方法
    Innershar C#中Skip和Take的用法
    DataTable.AcceptChanges方法有何用处
  • 原文地址:https://www.cnblogs.com/lidabo/p/7567965.html
Copyright © 2020-2023  润新知