此示例使用 C#,而不是 Java,但 NAT 遍历的概念与语言无关。
请参阅 Michael Lidgren 的网络库,该库内置了 NAT 遍历。
链接:http://code.google.com/p/lidgren-network-gen3/ 处理 NAT 遍历的特定 C# 文件:http://code.google.com/p/lidgren-network-gen3/source/browse/trunk/Lidgren.Network/NetNatIntroduction.cs
您发布的流程是正确的。它只适用于4种一般类型的NAT设备中的3种(我说一般,因为NAT行为并没有真正标准化):全锥NAT,受限锥体NAT和端口限制锥形NAT。NAT 遍历不适用于对称 NAT,对称 NAT 主要存在于企业网络中以增强安全性。如果一方使用对称 NAT,而另一方不使用,则仍然可以遍历 NAT,但这需要更多的猜测。对称 NAT 到对称 NAT 遍历非常困难 - 您可以在此处阅读有关它的论文。
但实际上,您描述的过程完全有效。我已经为我自己的远程屏幕共享程序实现了它(不幸的是,也是在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。
IPv4 地址(能够唯一命名大约 43 亿台计算机)已经用完了。聪明的人预见到了这个问题,并且,除其他原因外,发明了路由器来对抗IPv4地址耗尽,通过分配连接到自身的计算机网络1个共享IP地址。
有局域网IP。然后是广域网IP。LAN IP是局域网IP,它唯一标识本地网络中的计算机,例如连接到家用路由器的台式机,笔记本电脑,打印机和智能手机。WAN IP 唯一标识广域网中局域网外部的计算机 - 通常表示 Internet。因此,这些路由器为一组计算机分配了 1 个 WAN IP。每台计算机仍然有自己的 LAN IP。LAN IP 是您在命令提示符中键入并获取 .WAN IP 是您在连接到并获取 时所看到的。ipconfig
IPv4 Address . . . . . . . . 192.168.1.101
cmyip.com
128.120.196.204
正如无线电频谱被买断一样,整个IP范围也被机构和组织以及端口号买断和保留。同样,简短的消息是,我们不再有任何IPv4地址可用。
这与 NAT 遍历有什么关系?好吧,自从路由器发明以来,直接连接(端到端连接)已经有点...不可能,没有一些黑客。如果您的网络有 2 台计算机(计算机 A 和计算机 B)都共享 的 WAN IP,则连接到哪台计算机?我说的是一台外部计算机(比如 google.com)启动与 .答案是:没有人知道,路由器也不知道,这就是路由器断开连接的原因。如果计算机 A 启动了与 的连接,例如 ,那么情况就不同了。然后,路由器会记住具有 LAN IP 的计算机 A 启动了与 (google.com) 的连接。当计算机 A 的请求数据包离开路由器时,路由器实际上会将 LAN IP 重新写入路由器的 WAN IP。因此,当 google.com 收到计算机 A 的请求数据包时,它会看到路由器重写的发送方 IP,而不是计算机 A 的 LAN IP(google.com 视为要回复的 IP)。当 google.com 最终回复时,数据包到达路由器,路由器会记住(它有一个状态表)它正在等待来自 google.com 的回复,并适当地将数据包转发到计算机A。128.120.196.204
128.120.196.204
google.com
192.168.1.101
74.125.227.64
192.168.1.101
128.120.196.204
128.120.196.204
换句话说,当您启动连接时,您的路由器没有问题 - 您的路由器将记得将回复数据包转发回您的计算机(通过上述整个过程)。但是,当外部服务器启动与您建立连接时,路由器无法知道该连接适用于哪台计算机,因为计算机A和计算机B都共享...除非,有一个明确的规则指示路由器转发所有最初进入目标端口的数据包,现在转到计算机A,目标端口。这称为端口转发。不幸的是,如果您正在考虑对网络应用程序使用端口转发,这是不切实际的,因为您的用户可能不了解如何启用它,并且如果他们认为存在安全风险,他们可能不愿意启用它。UPnP 仅指允许您以编程方式启用端口转发的技术。遗憾的是,如果您正在考虑使用 UPnP 来端口转发网络应用程序,那也是不切实际的,因为 UPnP 并不总是可用,并且当它可用时,它可能不会默认打开。128.120.196.204
X
Y
那么解决方案是什么呢?解决方案是将整个流量代理到您自己的计算机上(您已仔细预先配置为可全局访问),或者想出一种方法来击败系统。第一个解决方案(我相信)称为TURN,它神奇地解决了所有连接问题,代价是为服务器群提供可用带宽。第二种解决方案称为 NAT 遍历,这是我们接下来要探索的内容。
前面,我描述了外部服务器(例如 google.com)启动与 .我说过,如果没有路由器有特定的规则来了解将Google的连接请求转发到哪台计算机,路由器就会简单地断开连接。这是一个普遍的场景,并不准确,因为有不同类型的NAT。(注意:路由器是您可以掉在地板上的实际物理设备。NAT(网络地址转换)是编程到路由器中的软件进程,有助于像树一样保存IPv4地址。因此,根据路由器使用的 NAT,连接方案会有所不同。路由器甚至可以组合 NAT 进程。128.120.196.204
有四种类型的 NAT 具有标准化行为:全锥 NAT、受限锥体 NAT、端口限制锥 NAT 和对称 NAT。除了这些类型之外,可能还有其他类型的 NAT 具有非标准化行为,但这种情况更为罕见。
注意:我对 NAT 不太熟悉...似乎有很多方法可以查看路由器,并且互联网上的信息在这个主题上非常分散。维基百科说,按完整,受限制和端口限制的锥体对NAT进行分类已被部分弃用。有一种叫做静态和动态NAT的东西...只是一堆我无法调和在一起的各种概念。尽管如此,以下模型适用于我自己的应用程序。您可以通过阅读下面和上面的链接以及整篇文章来了解有关NAT的更多信息。我不能发布更多关于他们的信息,因为我对他们了解不多。
希望一些网络大师能够纠正/添加输入,以便我们都能更多地了解这个神秘的过程。
要回答有关收集每个客户端的外部 IP 和端口的问题,请执行以下操作:
所有 UDP 数据包的标头在结构上相同,具有一个源 IP 和一个源端口。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 向端口 6000 的计算机 B 发送数据包。UDP 数据包上的源 IP 将为 192.168.1.101。这将是唯一的IP。“外部”在这里没有上下文,因为网络纯粹是局域网。在此示例中,广域网(如互联网)不存在。关于端口,由于我不确定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。服务器 S 在外部 IP 和端口方面看到了什么?服务器 S 将 128.120.196.204 视为 IP,而不是 192.168.1.101,服务器 S 将 32981 视为端口,而不是 3000。虽然这些不是计算机 A 用于发送数据包的原始 IP 和端口,但这些是要回复的正确 IP 和端口。当您收到数据包时,您只能知道WAN IP和重写的端口。如果这是你想要的(你只要求外部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);
因为 和 可以返回目标端口或源端口,具体取决于您设置的内容,在客户端(发送)计算机上,调用服务器(接收)计算机,并在服务器(接收)计算机上调用并返回客户端(发送)计算机。必须有一种方法可以在 中执行此操作。请详细说明这是否(并且不要返回您期望的源IP和端口)是您的实际障碍。这假设服务器是“标准”UDP 服务器(它不是 STUN 服务器)。getAddress()
getPort()
setAddress()
setPort()
setAddress()
setPort()
receive()
getAddress()
getPort()
进一步更新:
我读了您关于“如何使用STUN从一个客户端获取IP和端口并将其提供给另一个客户端”的更新?STUN 服务器不是为交换端点或执行 NAT 遍历而设计的。STUN 服务器旨在告诉您您的公共 IP、公共端口和 NAT 设备类型(无论是全锥 NAT、受限锥体 NAT 还是端口受限锥形 NAT)。我将负责交换端点和执行实际 NAT 遍历的中间人服务器称为“引入者”。在我的个人项目中,我实际上不需要使用STUN来执行NAT遍历。我的“引入器”(引入客户端 A 和 B 的中间人服务器)是侦听 UDP 数据报的标准服务器。当客户端 A 和 B 都向引入器注册自己时,引入者会读取其公共 IP 和端口以及专用 IP(如果它们在 LAN 上)。公共 IP 从数据报头中读取,就像所有标准 UDP 数据报一样。私有 IP 作为数据报有效负载的一部分写入,引入器仅将其作为有效负载的一部分读取。因此,关于STUN的有用性,您不需要依赖STUN来获取每个客户端的公共IP和公共端口 - 任何连接的套接字都可以告诉您这一点。我想说的是,STUN 仅用于确定客户端使用的 NAT 设备类型,以便您知道是否执行 NAT 遍历(如果 NAT 设备类型为全锥、受限或端口受限),或执行全输出 TURN 流量代理(如果 NAT 设备类型为对称)。
请详细说明您的障碍:如果您需要有关设计应用程序消息传递协议的最佳实践的建议,以及有关以有序和系统的方式阅读已接收消息字段的建议(基于您在下面发布的评论),您能否分享一下您目前的方法?