焦点速递!我在公司访问不了家里的电脑?

大家有没有想过,为什么在公司里访问不了家里的电脑?

这是因为IPv4地址有限,最大42亿个。为了更好的利用这有限的IP数量,网络分为局域网和广域网,将IP分为了私有IP和公网IP,一个局域网里的N多台机器都可以共用一个公网IP,从而大大增加了"可用IP数量"。


(资料图片仅供参考)

收发数据就像收发快递

当我们需要发送网络包的时候,在IP层,需要填入源IP地址,和目的IP地址,也就是对应快递的发货地址和收货地址。

IP报头里含有发送和接收IP地址

但是我们家里的局域网内,基本上都用192.168.xx.xx这样的私有IP

如果我们在发送网络包的时候,这么填。对方在回数据包的时候该怎么回?毕竟千家万户人用的都是192.168.0.1,网络怎么知道该发给谁?

所以肯定需要将这个192.168.xx私有IP转换成公有IP

局域网内用的是私有IP,公网用的都是公有IP。一个局域网里的私有IP想访问局域网外的公有IP,必然要做个IP转换,这是在哪里做的转换呢?

私有IP和公有IP在哪进行转换

答案是NAT设备,全称NetworkAddressTranslation,网络地址转换。基本上家用路由器都支持这功能。

我们来聊下它是怎么工作的。

NAT的工作原理

为了简单,我们假设你很富,你家里分到了一个公网 IP 地址20.20.20.20,对应配到了你家自带 NAT 功能的家用路由器上,你家里需要上网的设备有很多,比如你的手机,电脑都需要上网,他们构成了一个局域网,用的都是私有 IP,比如192.168.xx。其中你在电脑上执行ifconfig命令,发现家里的电脑IP是192.168.30.5。你要访问的公网 IP 地址是30.30.30.30

于是就有下面这样一张图

内网IP访问公网IP

当你准备发送数据包的时候,你的电脑内核协议栈就会构造一个IP数据包。这个IP数据包报头里的发送端IP地址填的就是192.168.30.5接收端IP地址就是30.30.30.30。将数据包发到NAT路由器中。

此时NAT路由器会将IP数据包里的源IP地址修改一下,私有IP地址192.168.30.5改写为公网IP地址20.20.20.20,这叫SNATSourceNetworkAddressTranslation,源地址转换)。并且还会在NAT路由器内部留下一条192.168.30.5 -> 20.20.20.20的映射记录,这个信息会在后面用到。之后IP数据包经过公网里各个路由器的转发,发到了接收端30.30.30.30,到这里发送流程结束。

SNAT

如果接收端处理完数据了,需要发一个响应给你的电脑,那就需要将发送端IP地址填上自己的30.30.30.30,将接收端地址填为你的公网IP地址20.20.20.20,发往NAT路由器。NAT路由器收到公网来的消息之后,会检查下自己之前留下的映射信息,发现之前留下了这么一条192.168.30.5 -> 20.20.20.20记录,就会将这个数据包的目的IP地址修改一下,变成内网IP地址192.168.30.5, 这也叫DNATDestinationNetworkAddressTranslation,目的地址转换)。之后将其转发给你的电脑上。

DNAT

整个过程下来,NAT悄悄的改了IP数据包的发送和接收端IP地址,但对真正的发送方和接收方来说,他们却对这件事情,一无所知

这就是NAT的工作原理。

NAPT的原理

到这里,相信大家都有一个很大的疑问。

局域网里并不只有一台机器,局域网内每台机器都在 NAT 下留下的映射信息都会是192.168.xx.xx -> 20.20.20.20,发送消息是没啥事,但接收消息的时候就不知道该回给谁了。

NAT的问题

这问题相当致命,因此实际上大部分时候不会使用普通的 NAT

那怎么办呢?

问题出在我们没办法区分内网里的多个网络连接。

于是乎。

我们可以加入其他信息去区分内网里的各个网络连接,很自然就能想到端口。

但IP数据包(网络层)本身是没有端口信息的。常见的传输层协议TCP和UDP数据报文里才有端口的信息。

TCP 报头有端口号
UDP 报头也有端口号

于是流程就变成了下面这样子。

当你准备发送数据包的时候,你的电脑内核协议栈就会先构造一个TCP或者UDP数据报头,里面写入端口号,比如发送端口是5000,接收端口是3000,然后在这个基础上,加入 IP 数据报头,填入发送端和接收端的 IP 地址。

那数据包长这样。

数据包的构成

假设,发送端IP地址填的就是192.168.30.5接收端IP地址就是30.30.30.30

将数据包发到NAT路由器中。

此时NAT路由器会将IP数据包里的源IP地址和端口号修改一下,从192.168.30.5:5000改写成20.20.20.20:6000。并且还会在NAT路由器内部留下一条192.168.30.5:5000 -> 20.20.20.20:6000的映射记录。之后数据包经过公网里各个路由器的转发,发到了接收端30.30.30.30:3000,到这里发送流程结束。

NAPT发送数据

接收端响应时,就会在数据包里填入发送端地址是30.30.30.30:3000,将接收端20.20.20.20:6000,发往NAT路由器。NAT路由器发现下自己之前留下过这么一条192.168.30.5:5000 -> 20.20.20.20:6000的记录,就会将这个数据包的目的IP地址和端口修改一下,变回原来的192.168.30.5:5000。之后将其转发给你的电脑上。

NAPT接收数据

如果局域网内有多个设备,他们就会映射到不同的公网端口上,毕竟端口最大可达 65535,完全够用。这样大家都可以相安无事。

像这种同时转换IP 和端口的技术,就是NAPT(Network Address Port Transfer ,网络地址端口转换)。

看到这里,问题就来了。

那这么说只有用到端口的网络协议才能被 NAT 识别出来并转发?

但这怎么解释ping命令?ping基于 ICMP 协议,而 ICMP 协议报文里并不带端口信息。我依然可以正常的ping通公网机器并收到回包。

ping报头

事实上针对 ICMP 协议,NAT 路由器做了特殊处理。ping 报文头里有个Identifier的信息,它其实指的是放出ping命令的进程id

对 NAT 路由器来说,这个Identifier的作用就跟端口一样。

另外,当我们去抓包的时候,就会发现有两个Identifier,一个后面带个BE(Big Endian),另一个带个LE(Little Endian)

其实他们都是同一个数值,只不过大小端不同,读出来的值不一样。就好像同样的数字345,反着读就成了543。这是为了兼容不同操作系统(比如linux和Windows)下大小端不同的情况。

内网穿透是什么

看到这里,我们大概也发现了。使用了NAT上网的话,前提得内网机器主动请求公网 IP,这样NAT才能将内网的IP 端口转成外网IP 端口

反过来公网的机器想主动请求内网机器,就会被拦在 NAT 路由器上,此时由于 NAT 路由器并没有任何相关的 IP 端口的映射记录,因此也就不会转发数据给内网里的任何一台机器。

举个现实中的场景就是,你在你家里的电脑上启动了一个 HTTP 服务,地址是192.168.30.5:5000,此时你在公司办公室里想通过手机去访问一下,却发现访问不了。

那问题就来了,有没有办法让外网机器访问到内网的服务?

有。

大家应该听过一句话叫,"没有什么是加中间层不能解决的,如果有,那就再加一层"。

放在这里,依然适用。

说到底,因为 NAT 的存在,我们只能从内网主动发起连接,否则 NAT 设备不会记录相应的映射关系,没有映射关系也就不能转发数据。

所以我们就在公网上加一台服务器 x,并暴露一个访问域名,再让内网的服务主动连接服务器 x,这样NAT路由器上就有对应的映射关系。接着,所有人都去访问服务器x,服务器x将数据转发给内网机器,再原路返回响应,这样数据就都通了。这就是所谓的内网穿透

像上面提到的服务器x,你也不需要自己去搭,已经有很多现成的方案,花钱就完事了,比如花某壳。

内网穿透

到这里,我们就可以回答这个问题:

为什么我在公司里访问不了家里的电脑?

那是因为家里的电脑在局域网内,局域网和广域网之间有个 NAT 路由器。由于 NAT 路由器的存在,外网服务无法主动连通局域网内的电脑。

两个内网的聊天软件如何建立通讯

好了,问题就叒来了。

我家机子是在我们小区的局域网里,班花家的机子也是在她们小区的局域网里。都在局域网里,且 NAT 只能从内网连到外网,那我电脑上登录的QQ是怎么和班花电脑里的QQ连上的呢?

两个局域网内的服务无法直接连通

上面这个问法其实是存在个误解,误以为两个 qq 客户端应用是直接建立连接的。

然而实际上并不是,两个 qq 客户端之间还隔了一个服务器。

聊天软件会主动与公网服务器建立连接

也就是说,两个在内网的客户端登录 qq 时都会主动向公网的聊天服务器建立连接,这时两方的 NAT 路由器中都会记录有相应的映射关系。当在其中一个 qq 上发送消息时,数据会先到服务器,再通过服务器转发到另外一个客户端上。反过来也一样,通过这个方式让两台内网的机子进行数据传输。

两个内网的应用如何直接建立连接

上面的情况,是两个客户端通过第三方服务器进行通讯,但有些场景就是要抛开第三端,直接进行两端通信,比如 P2P 下载,这种该怎么办呢?

这种情况下,其实也还是离不开第三方服务器的帮助。

假设还是 A 和 B 两个局域网内的机子,A内网对应的NAT设备叫NAT_A,B内网里的NAT设备叫NAT_B,和一个第三方服务器server

流程如下。

step1和2: A 主动去连 server,此时 A 对应的NAT_A就会留下 A 的内网地址和外网地址的映射关系,server 也拿到了 A 对应的外网 IP 地址和端口。

step3和4: B 的操作和 A 一样,主动连第三方 server,NAT_B内留下 B 的内网地址和外网地址的映射关系,然后 server 也拿到了 B 对应的外网 IP 地址和端口。

step5和step6以及step7: 重点来了。此时 server 发消息给 A,让 A 主动发UDP消息到 B 的外网 IP 地址和端口。此时 NAT_B 收到这个 A 的UDP数据包时,这时候根据 NAT_B 的设置不同,导致这时候有可能NAT_B 能直接转发数据到 B,那此时 A 和 B 就通了。但也有可能不通,直接丢包,不过丢包没关系,这个操作的目的是给 NAT_A 上留下有关 B 的映射关系

step8和step9以及step10: 跟step5一样熟悉的配方,此时 server 再发消息给 B,让 B 主动发UDP消息到 A 的外网 IP 地址和端口。NAT_B 上也留下了关于 A 的映射关系,这时候由于之前 NAT_A 上有过关于 B 的映射关系,此时 NAT_A 就能正常接受 B 的数据包,并将其转发给 A。到这里 A 和 B 就能正常进行数据通信了。这就是所谓的NAT 打洞

step11: 注意,之前我们都是用的UDP 数据包,目的只是为了在两个局域网的 NAT 上打个洞出来,实际上大部分应用用的都是 TCP 连接,所以,这时候我们还需要在 A 主动向 B 发起 TCP 连接。到此,我们就完成了两端之间的通信。

NAT打洞

这里估计大家会有疑惑。

端口已经被udp用过了,TCP再用,那岂不是端口重复占用(address already in use)?

其实并不会,端口重复占用的报错常见于两个TCP连接在不使用SO_REUSEADDR的情况下,重复使用了某个IP端口。而UDP和TCP之间却不会报这个错。之所以会有这个错,主要是因为在一个linux内核中,内核收到网络数据时,会通过五元组(传输协议,源IP,目的IP,源端口,目的端口)去唯一确定数据接受者。当五元组都一模一样的时候,内核就不知道该把数据发给谁。而UDP和TCP之间"传输协议"不同,因此五元组也不同,所以也就不会有上面的问题。

五元组

NAPT还分为好多种类型,上面的 NAT 打洞方案,都能成功吗?

关于NAPT,确实还细分为好几种类型,比如完全锥形NAT和限制型NAT啥的,但这并不是本文的重点。所以我就略过了。我们现在常见的都是锥形NAT。上面的打洞方案适用于大部分场景,这其中包括限制最多的端口受限锥形NAT

总结

• IPV4 地址有限,但通过 NAT 路由器,可以使得整个内网 N 多台机器,对外只使用一个公网 IP,大大节省了 IP 资源。

•内网机子主动连接公网 IP,中间的 NAT 会将内网机子的内网 IP 转换为公网 IP,从而实现内网和外网的数据交互。

•普通的 NAT 技术,只会修改网络包中的发送端和接收端 IP 地址,当内网设备较多时,将有可能导致冲突。因此一般都会使用 NAPT 技术,同时修改发送端和接收端的IP 地址和端口

•由于 NAT 的存在,公网 IP 是无法访问内网服务的,但通过内网穿透技术,就可以让公网 IP 访问内网服务。一波操作下来,就可以在公司的网络里访问家里的电脑。

最后留个问题,有了 NAT 之后,原本并不富裕的 IPv4 地址突然就变得非常够用了。

那我们为什么还需要 IPv6?

另外 IPv6 号称地址多到每粒沙子都能拥有自己的 IP 地址,那我们还需要 NAT 吗?


欢迎学编程的朋友们加入鱼皮的,鱼皮会 1 对 1 解决你的问题,直播带你做出项目、为你定制学习计划和求职指导,还能获取海量编程学习资源,和上万名学编程的同学共享知识、交流进步。

往期推荐

关键词: 我在公司 家用路由器

推荐DIY文章
主机存在磨损或划痕风险 PICO4便携包宣布召回
穿越湖海!特斯拉Cybertruck电动皮卡可以当“船”用
vivoXFold+折叠旗舰开售 配备蔡司全焦段旗舰四摄
飞凡R7正式上市 全系标配换电架构
中兴Axon30S开售 拥有黑色蓝色两款配色
荣耀MagicBookV14 2022正式开售 搭载TOF传感器
it