此示例使用 C# 编写,而不是 Java 编写,但 NAT 遍历的概念与语言无关。
查看 Michael Lidgren 的内置 NAT 遍历的网络库。
链接:http://code.google.com/p/lidgren-network-gen3/
处理 NAT Traversal 的特定 C# 文件:http://code.google.com/p/lidgren-network-gen3/source/browse/trunk/Lidgren.Network/NetNatIntroduction.cs
您发布的流程是正确的。它仅适用于NAT devices 的 4 种通用类型中的 3 种(我之所以说通用,是因为 NAT 行为并未真正标准化):Full-Cone NATs、Restricted-Cone NATs 和 Port-Restricted-Cone NATs。 NAT 遍历不适用于对称 NAT,对称 NAT 主要存在于公司网络中以增强安全性。如果一方使用对称 NAT 而另一方不使用,仍然可以穿越 NAT,但需要更多猜测。对称 NAT 到对称 NAT 的穿越非常困难 - you can read a paper about it here。
但实际上,您描述的过程完全有效。我已经为my own remote screen sharing program 实现了它(不幸的是,也在 C# 中)。只需确保您已禁用 Windows 防火墙(如果您使用的是 Windows)和第三方防火墙。但是,是的,我可以很高兴地确认它会起作用。
阐明NAT穿越的过程
我编写此更新是为了向您和未来的读者阐明 NAT 穿越的过程。希望这可以是对历史和过程的清晰总结。
部分参考来源:http://think-like-a-computer.com/2011/09/16/types-of-nat/、http://en.wikipedia.org/wiki/Network_address_translation、http://en.wikipedia.org/wiki/IPv4、http://en.wikipedia.org/wiki/IPv4_address_exhaustion。
能够唯一命名大约 43 亿台计算机的 IPv4 地址已经用完。聪明的人预见到了这个问题,并发明了路由器来对抗 IPv4 地址耗尽,方法是为连接到自身的计算机网络分配一个共享 IP 地址。
有 LAN IP。然后是 WAN IP。 LAN IP 是局域网 IP,可唯一标识本地网络中的计算机,例如连接到家庭路由器的台式机、笔记本电脑、打印机和智能手机。 WAN IP 在广域网中唯一地标识局域网之外的计算机 - 通常被认为是指 Internet。因此这些路由器为一组计算机分配了 1 个 WAN IP。每台计算机仍然有自己的 LAN IP。当您在命令提示符中键入 ipconfig 并获得 IPv4 Address . . . . . . . . 192.168.1.101 时,您会看到 LAN IP。当您连接到 cmyip.com 并获得 128.120.196.204 时,您会看到 WAN IP。
就像the radio spectrum is bought out 一样,整个IP 范围也被机构和组织as well as port numbers 买断和保留。简短的信息是,我们没有更多的 IPv4 地址可供使用。
这与 NAT 穿越有什么关系?好吧,自从发明了路由器以来,直接连接(end-to-end connectivity)在某种程度上......不可能,没有一些黑客攻击。如果您有一个由两台计算机(计算机 A 和计算机 B)组成的网络,它们都共享 128.120.196.204 的 WAN IP,连接到哪台计算机?我说的是外部计算机(例如 google.com)启动到128.120.196.204 的连接。答案是:没人知道,路由器也不知道,这就是路由器断开连接的原因。如果计算机 A 启动到 google.com 的连接,那么情况就不同了。然后路由器会记住具有 LAN IP 192.168.1.101 的计算机 A 发起了与 74.125.227.64 (google.com) 的连接。当计算机 A 的请求包离开路由器时,路由器实际上重写 LAN IP 192.168.1.101 到路由器的WAN IP 128.120.196.204。因此,当 google.com 收到计算机 A 的请求数据包时,它看到的是路由器重写的发送方 IP,而不是计算机 A 的 LAN IP(google.com 将128.120.196.204 视为要回复的 IP)。当 google.com 最终回复时,数据包到达路由器,路由器记住(它有一个状态表)它期待来自 google.com 的回复,并将数据包适当地转发到计算机 A .
换句话说,当您启动连接时,您的路由器没有问题 - 您的路由器会记住将回复数据包转发回您的计算机(通过上述整个过程)。但是,当外部服务器向您发起连接时,路由器无法知道连接的目的是哪台计算机,因为计算机 A 和计算机 B 都共享 128.120.196.204 的 WAN IP。 . 除非,有一个明确的规则指示路由器将所有最初发往目标端口X 的数据包转发到目标端口Y 的计算机A。这称为端口转发。不幸的是,如果您正在考虑为您的网络应用程序使用端口转发,这是不切实际的,因为您的用户可能不了解如何启用它,并且如果他们认为这是一个安全风险,他们可能不愿意启用它。 UPnP 只是指允许您以编程方式启用端口转发 的技术。不幸的是,如果您正在考虑使用 UPnP 来端口转发您的网络应用程序,那么它也不实用,因为 UPnP 并不总是可用,而且当它可用时,它可能不会默认打开。
那么解决办法是什么?解决方案是通过您自己的计算机代理您的全部流量(您已经仔细预配置为全局可访问),或者想出一种击败系统的方法。第一个解决方案(我相信)称为TURN,它以提供可用带宽的服务器场为代价神奇地解决了所有连接问题。第二种解决方案称为 NAT 穿越,这是我们接下来要探索的。
之前,我描述了外部服务器(例如 google.com)发起与128.120.196.204 的连接的过程。我说过,如果路由器没有特定的规则来了解将 google 的连接请求转发到哪台计算机,路由器就会简单地断开连接。这是一个笼统的场景,并不准确,因为有不同类型的 NAT。 (注意:路由器是您可以放在地板上的实际物理设备。NAT(网络地址转换)是一个编程到路由器中的软件过程,有助于保存 IPv4 地址,如树)。因此,根据路由器使用的哪个 NAT,连接方案会有所不同。路由器甚至可以合并 NAT 进程。
具有标准化行为的 NAT 有四种类型:Full-Cone NAT、Restricted-Cone NAT、Port-Restricted-Cone NAT 和对称 NAT。除了这些类型之外,还可以有其他类型的具有非标准化行为的 NAT,但这种情况比较少见。
注意:我对 NAT 不太熟悉……看起来有很多方法可以查看路由器,并且互联网上的信息在这个主题上非常分散。维基百科说,通过完整、受限和端口受限的锥体对 NAT 进行分类已经有些过时了?有一种叫做静态和动态 NAT 的东西……只是一堆我无法协调的各种概念。不过,以下模型适用于我自己的应用程序。您可以通过阅读下面和上面的链接以及整篇文章来了解更多关于 NAT 的信息。我不能发布更多关于它们的信息,因为我对它们了解不多。
希望一些网络专家更正/添加输入,以便我们都能更多地了解这个神秘的过程。
回答您的问题关于收集每个客户端的外部 IP 和端口:
The headers of all UDP packets are structured the same 带有 one 源 IP 和 one 源端口。 UDP 数据包标头不包含“内部”源 IP 和“外部”源 IP。 UDP 数据包头只包含一个源 IP。如果您想获得“内部”和“外部”源 IP,您需要实际发送内部源 IP 作为有效负载的一部分。 但听起来您不需要内部源 IP 和端口.正如您的问题所述,听起来您只需要一个外部 IP 和端口。这意味着您的解决方案只需读取源 IP 并像字段一样从数据包中取出端口。
以下两种情况(它们并没有真正解释其他任何事情):
局域网通信
计算机 A 的 LAN IP 为 192.168.1.101。计算机 B 的 LAN IP 为 192.168.1.102。计算机 A 从端口 3000 向计算机 B 的端口 6000 发送一个数据包。UDP 数据包上的源 IP 将为 192.168.1.101。这将是唯一的 IP。 “外部”在这里没有上下文,因为网络纯粹是局域网。在此示例中,不存在广域网(如 Internet)。关于端口,因为我不确定 NAT,我不确定数据包上的端口是否为 3000。NAT 设备可能将数据包的端口从 3000 重写为随机端口比如 49826。无论哪种方式,你都应该使用数据包上的任何端口来回复——这就是你应该用来回复的。因此,在这个 LAN 通信示例中,您只需要发送一个 IP - LAN IP,因为这才是最重要的。您不必担心端口 - 路由器会为您处理。当您收到数据包时,您只需从数据包中读取它即可收集唯一的 IP 和端口。
广域网通信
计算机 A 的 LAN IP 还是 192.168.1.101。计算机 B 的 LAN IP 还是 192.168.1.102。计算机 A 和计算机 B 将共享一个 WAN IP 128.120.196.204。服务器 S 是一台服务器,一台全球可访问的计算机,比方说,一台 Amazon EC2 服务器,其 WAN IP 为 1.1.1.1。服务器 S 可能有一个 LAN IP,但这无关紧要。计算机 B 也无关紧要。
计算机 A 从端口 3000 向服务器 S 发送一个数据包。在离开路由器的途中,计算机 A 的数据包源 LAN IP 被重新写入路由器的 WAN IP。路由器也将300的源端口改写为32981。Server S看到的外部IP和端口是什么?服务器 S 将 128.120.196.204 视为 IP,而不是 192.168.1.101,服务器 S 将 32981 视为端口,而不是 3000。虽然这些不是计算机 A 用于发送数据包的原始 IP 和端口,但这些是正确的 IP和要回复的端口。收到包时,只能知道WAN IP和改写的端口。如果这就是您想要的(您只要求 external IP 和端口),那么您就设置好了。否则,如果您还想要发件人的内部 IP,则需要将其作为普通数据从您的标头中分开传输。
代码:
如上所述(在回答您的问题关于收集外部 IP 的下方),要收集每个客户端的外部 IP 和端口,您只需从数据包中读取它们即可。每个发送的数据报总是有发送者的源IP和源端口;您甚至不需要花哨的自定义协议,因为始终包含这两个字段 - 根据定义,每个单独的 UDP 数据包都必须具有这两个字段。
// Java language
// Buffer for receiving incoming data
byte[] inboundDatagramBuffer = new byte[1024];
DatagramPacket inboundDatagram = new DatagramPacket(inboundDatagramBuffer, inboundDatagramBuffer.length);
// Source IP address
InetAddress sourceAddress = inboundDatagram.getAddress();
// Source port
int sourcePort = inboundDatagram.getPort();
// Actually receive the datagram
socket.receive(inboundDatagram);
因为getAddress() 和getPort() 可以返回目标端口或源端口,具体取决于您将其设置为什么,在客户端(发送)机器上,调用setAddress() 和setPort() 到服务器(接收) 机器,然后在服务器(接收)机器上,将setAddress() 和setPort() 调用回客户端(发送)机器。在receive() 中一定有办法做到这一点。请详细说明这(getAddress() 和 getPort() 不返回您期望的源 IP 和端口)是否是您的实际障碍。这是假设服务器是“标准”UDP 服务器(它不是 STUN 服务器)。
进一步更新:
我阅读了您关于“如何使用 STUN 从一个客户端获取 IP 和端口并将其提供给另一个客户端”的更新? STUN 服务器并非旨在交换端点或执行 NAT 遍历。 STUN 服务器旨在告诉您您的公共 IP、公共端口和 NAT 设备的类型(无论是 Full-Cone NAT、Restricted-Cone NAT 还是 Port-Restricted Cone NAT)。我将负责交换端点和执行实际 NAT 遍历的中间人服务器称为“介绍人”。在my personal project 中,我实际上并不需要使用 STUN 来执行 NAT 遍历。我的“介绍人”(介绍客户端 A 和 B 的中间人服务器)是侦听 UDP 数据报的标准服务器。当客户端 A 和 B 都向介绍人注册时,介绍人会读取他们的公共 IP 和端口以及私有 IP(如果他们在 LAN 上)。与所有标准 UDP 数据报一样,从数据报头中读取公共 IP。私有 IP 作为数据报有效负载的一部分写入,引入者只是将其作为有效负载的一部分读取。所以,关于 STUN 的用处,你不需要依赖 STUN 来获取每个客户端的公共 IP 和公共端口——任何连接的套接字都可以告诉你这一点。我想说 STUN 仅用于确定您的客户端使用哪种类型的 NAT 设备,以便您知道是执行 NAT 遍历(如果 NAT 设备类型是 Full-Cone、Restricted 或 Port-Restricted),还是执行全面 TURN 流量代理(如果 NAT 设备类型为对称)。
请详细说明您的障碍:如果您需要有关设计应用程序消息传递协议的最佳实践的建议,以及有关以有序和系统的方式阅读收到的消息的字段的建议(基于您在下面发布的评论),您能分享你目前的方法?