NAT 如何决定哪些连接是入站的,哪些是出站的?

网络工程 通讯协议 纳特 协议理论 传输协议 第4层
2021-07-12 23:44:22

我正在构建一个 P2P 应用程序,它需要能够访问 NAT 后面的节点。此 NAT 不允许入站连接,因此网络外部的节点无法访问 NAT 后面的节点。

我对这个问题的解决方案是,NAT 后面的节点会向外部节点伸出手,而外部节点,当时间准备好时,使用预先建立的 TCP 连接连接到 NAT 内部的节点。由于此连接将由 NAT 内的节点建立,因此不会被 NAT 阻止。

这是我的问题。我不知道下划线的解释中哪些是正确的,或者两者是否都是错误的。

解释#1

这里一个TCP连接,有效地管的概念,而当NAT内的一个节点,使对外部世界的任何一个要求,即管打开,响应被放了进去,和管永远关闭。

结果:在该管关闭后,NAT 将不允许从外部节点到客户端的任何进一步 TCP 连接。

解释#2

没有 TCP 连接之类的东西,而是 TCP 传输。由于 TCP 连接被定义为 alocal ip:port和 a remote ip:port,在 NAT 后面的节点调用外部节点后,外部节点可以使用客户端发出请求的相同端口进行回调,这将“计数”为相同的 TCP连接,而不是 NAT 主动调用。

结果: NAT 通过查看历史记录有效地猜测此连接是否是对 NAT 内部节点请求的响应。由于 NAT 后面的节点进行了第一次呼叫,因此外部节点可以使用相同的客户端端口进行回调,并且可以正常工作。

我的心智模型更接近 #1,但是当我检查与 Wireshark 的连接时,它们确实显示为单独的 TCP 连接,或者至少显示为单独的实体。

它更接近#1 还是#2,还是我绝望地感到困惑?

3个回答

TCP是面向连接的协议,只能通过连接进行通信。

在开始使用 TCP 进行编程之前,首先了解 TCP 的工作原理会很有帮助,您应该从RFC 793,传输控制协议开始,它是 TCP 的定义。

RFC 解释了套接字和连接:

多路复用:

为了允许单个主机内的多个进程同时使用 TCP 通信设施,TCP 在每个主机内提供了一组地址或端口。与来自互联网通信层的网络和主机地址连接,形成一个套接字。一对套接字唯一标识每个连接。也就是说,一个套接字可以同时用于多个连接。

端口与进程的绑定由每个主机独立处理。然而,将经常使用的进程(例如,“记录器”或分时服务)附加到公知的固定套接字被证明是有用的。然后可以通过已知地址访问这些服务。建立和学习其他进程的端口地址可能涉及更多的动态机制。

连接:

上面描述的可靠性和流量控制机制要求 TCP 为每个数据流初始化和维护某些状态信息。这些信息(包括套接字、序列号和窗口大小)的组合称为连接。每个连接由一对标识其两侧的套接字唯一指定。

当两个进程希望通信时,它们的 TCP 必须首先建立连接(初始化每一方的状态信息)。当他们的通信完成时,连接被终止或关闭以释放资源以供其他用途。

由于必须在不可靠的主机和不可靠的互联网通信系统之间建立连接,因此使用基于时钟的序列号的握手机制来避免连接的错误初始化。

就外部 TCP 对等体而言,它正在连接到 NAT 设备的外部地址,即使该设备根据内部设备发起的连接转发到内部的另一个 TCP 对等体。

您还可以研究RFC 5382,TCP 的 NAT 行为要求

只要两端的软件进程保持套接字打开,TCP 连接就被认为是“有效的”。

当一端或两端的进程关闭套接字(正常或连接因某种原因中止)时,这会在线上转换为设置了 FIN 或 RST 标志的 TCP 数据包。

NAT 路由器上的 NAT 实现会查找 FIN 和 RST 标志,当它看到带有这些标志的数据包时,它会“关闭漏洞”。在这之后,客户端必须发起一个新的连接以“打开一个新的洞”。

总而言之,只要您的客户端和服务器保持套接字打开,NAT 关联就会保持活动状态。

当我在这里写到“通常”时,我想到的是具有合理配置的普通消费者 WLAN-NAT 路由器,或者一些具有默认设置的简单 Linux 网络。像往常一样,这可以根据需要变得复杂或复杂。由于这个问题非常基本,这对我来说似乎最有意义,而不是直接寻求任何更复杂的企业级 NAT 解决方案。

您已经接受了一个答案,但让我尝试直接解决您提出的问题:

NAT 如何决定哪些连接是入站的,哪些是出站的?

决策(对于路由器中的任何决策)的基础是某种形式或方式的一组规则。在这种情况下,对于涉及的每个接口(即内部 LAN 接口与外部 WAN/上行链路接口),管理员将实施规则。这些规则完全不同,即 LAN 接口的规则看起来与 WAN 接口的规则大不相同。

知道数据包来自哪里以及它去哪里是路由器工作的基本要素。

让我从一个

例子

维基百科的 NAT 页面有很多关于这个问题的文字,但一个简单的案例(一个简单的公司 LAN 与单个 DSL 上行链路)是这样的:

  1. 客户端 PC 尝试启动到基于 Internet 的服务器的 HTTP 连接,例如 198.51.100.20。PC 本身有一个非路由地址,如 192.0.2.2。廉价的 DSL 路由器有两个接口,一个是内部接口 (192.0.2.1),一个是外部接口 (203.0.113.10,很可能经常更改并由 DLS 提供商通过某些本地链接协议提供)。因此 PC 通过其默认网关 192.0.2.1 向 198.51.100.20:80 发送一个 SYN 数据包。
  2. 路由器在其接口 192.0.2.1 接收数据包,就像根本不涉及 NAT 一样。它已配置为在此接口上执行 NAT,因此它继续执行以下操作:
    • 发明一个新的端口号,目前尚未使用。例如 12345。
    • 将 IP 标头中的“发送者”地址更改为 203.0.113.10。
    • 记住 TCP 头中的原始发送端端口号(由 PC 提供),我们称之为 4321。
    • 更改 TCP 标头以包含 12345 发送方端口号。
    • 在其 NAT 转换表中添加一个条目 (12345; 192.0.2.2; 4321)。
    • 将数据包以愉快的方式发送到其自己的上行链路/网关。
  3. 198.51.100.20 最终接收到数据包,注意到它是一个 SYN(“建立新连接”)并向发送方发回 SYN-ACK 消息。从它的角度来看,这是IP地址203.0.113.10,TCP目的端口为12345。
  4. 路由器在其 WAN 接口上接收此数据包。WAN 接口已配置为解析这样的 NAT 地址。然后路由器...
    • ...检查其 NAT 转换表,找到条目...
    • ...将数据包修改为目的地 192.0.2.2...
    • ...将 TCP 目标端口修复回 4321 ...
    • ...并沿着它的快乐方式发送它(在 LAN 接口上)。
  5. PC 收到数据包,但看不到任何有关 NAT 过程的信息。包看起来只是一样,如果198.51.100.20送了它,仿佛NAT路由器是根本不存在。

根本没有出现“连接”的话题。NAT 路由器(以其最简单的形式)不需要关心消息的内容。它关心发送方和接收方的 IP 地址和端口,但不关心其他。(当然:这可能会跳过所有与安全和性能相关的问题;但这是关于非常基本的原则,就像这个问题中手头的原则。)

那么路由器是怎么知道的呢?

路由器根本不需要知道“连接”。事实上,对于无连接的 UDP 协议(​​UDP 打洞)存在类似为 TCP 描述的类似过程,或者实际上可以为任何在传输层中具有端口号之类的协议实现。

路由器需要知道与 NAT 相关的传输层协议(TCP、UDP 等)的原因主要是端口本身不是 IP 的一部分;和端口是使(这种)NAT 很容易实现的“黑客”的原因。

所以,对于你的问题:

NAT 如何决定哪些连接是入站的,哪些是出站的?

根据定义,出站连接以出现在 LAN 接口上的 SYN 数据包(或在 UDP 的情况下为初始 UDP 穿孔)开头。在 NAT 的情况下称它们为“连接”有点过分;它们最终只是作为 NAT 转换表中的临时条目(加上单个 NAT 路由器可能采用的任何安全/性能添加)。

到目前为止,我在答案中使用的场景中不存在入站连接当然有 NAT 的变体可以做到这一点;例如,您可以使用 LAN 接口上的特定 IP:PORT 静态识别路由器 WAN 接口上的端口,这使得在 NATted LAN 内运行服务器成为可能。廉价的消费类 DSL/WLAN 路由器通常也支持这一点。使用“真正的”路由器,您显然能够以您喜欢的任何形式或方式配置它们。

进一步的入站/出站IP 数据包与示例中给出的数据包没有什么不同。一旦初始 SYN 握手完成并且路由器在其转换表中有条目,它将通过(使用与示例中解释的相同的转换)所有进一步的两个方向的数据包。

如果在这样建立的 TCP 连接的上下文中,服务器想要向客户端发送数据(这是完全可能的 - TCP 是双向的),就 NAT 路由器而言,这些只是进一步的 IP 数据包。它不会真正关心这些数据包的内容(即,它们是否包含某些有效载荷,或者只是 TCP 的“管理”数据包或其他任何内容)。

当您放置路由器时,路由器绝不会以某种方式“关闭管子”。显然,路由器会知道什么时候可以从转换表中清除条目(可能是当它注意到结束连接的 FIN 握手时,或者由于某些超时或某些错误状态),但从头到尾它是一件连续的事情。