36

假设我有两台电脑。

他们通过ice4j.

一个客户端监听,另一个发送一些字符串。

我希望通过 UPD 打孔看到这种情况发生:

Let A be the client requesting the connection

Let B be the client that is responding to the request

Let S be the ice4j STUN server that they contact to initiate the connection
--
A sends a connection request to S

S responds with B's IP and port info, and sends A's IP and port info to B

A sends a UDP packet to B, which B's router firewall drops but it still
punches a hole in A's own firewall where B can connect

B sends a UDP packet to A, that both punches a hole in their own firewall,
and reaches A through the hole that they punched in their own firewall

A and B can now communicate through their established connection without 
the help of S

任何人都可以发布如何通过对称 NAT 进行打孔的伪示例吗?假设将有服务器 S 帮助猜测端口号并在客户端 A 和 B 之间建立连接。

如果您也考虑双 NAT,那就太好了。

笔记:

您可以使用 STUN 来发现 IP 和端口,但您必须编写自己的代码,通过keepalive技术将 IP:Port 发送到您的服务器。

一旦一个客户端通过服务器上的唯一 ID 识别另一个客户端,它将提供另一个客户端的 IP:端口信息,以便 UDP 打孔它需要发送和接收的数据。

小更新:

有一个即将出现的库供 java 检查一下:
https ://github.com/htwg/UCE#readme

4

4 回答 4

52

这个例子是在 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/主干/Lidgren.Network/NetNatIntroduction.cs

您发布的过程是正确的。它只适用于 4 种通用类型的NAT 设备中的 3 种(我说通用是因为 NAT 行为并未真正标准化):Full-Cone NAT、Restricted-Cone NAT 和 Port-Restricted-Cone NAT。NAT 遍历不适用于对称 NAT,对称 NAT 主要存在于公司网络中以增强安全性。如果一方使用对称 NAT 而另一方不使用,仍然可以穿越 NAT,但需要更多猜测。对称 NAT 到对称 NAT 的穿越非常困难 -您可以在此处阅读有关它的论文

但实际上,您描述的过程完全有效。我已经为我自己的远程屏幕共享程序(不幸的是,也在 C# 中)实现了它。只需确保您已禁用 Windows 防火墙(如果您使用的是 Windows)和第三方防火墙。但是,是的,我可以很高兴地确认它会起作用。

阐明 NAT Traversal 的过程

我编写此更新是为了向您和未来的读者阐明 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。LAN IP 是您在输入ipconfig命令提示符并获取IPv4 Address . . . . . . . . 192.168.1.101. WAN IP 是您在连接cmyip.com并获取128.120.196.204.

正如无线电频谱被买断一样,整个 IP 范围也被机构和组织以及端口号买断和保留。简短的信息是,我们没有更多的 IPv4 地址可用。

这与 NAT 穿越有什么关系?好吧,自从发明了路由器以来,直接连接(端到端连接)在某种程度上......不可能,没有一些黑客攻击。如果您有一个由 2 台计算机(计算机 A 和计算机 B)组成的网络,它们都共享 的 WAN IP 128.120.196.204,连接到哪台计算机?我说的是一台外部计算机(比如 google.com)启动128.120.196.204. 答案是:没有人知道,路由器也不知道,这就是路由器断开连接的原因。如果计算机 A发起与 的连接google.com,那么情况就不同了。然后路由器会记住具有 LAN IP 的计算机 A192.168.1.101发起了一个连接到74.125.227.64(谷歌网站)。随着计算机A的请求包离开路由器,路由器实际上将LAN IP重新写入192.168.1.101路由器的WAN IP 128.120.196.204。因此,当 google.com 收到计算机 A 的请求数据包时,它看到的是路由器重写的发件人 IP,而不是计算机 A 的 LAN IP(google.com128.120.196.204视为要回复的 IP)。当 google.com 最终回复时,数据包到达路由器,路由器记住(它有一个状态表)它期待来自 google.com 的回复,并适当地将数据包转发到计算机 A。

换句话说,当启动连接时,您的路由器没有问题 - 您的路由器会记住将回复数据包转发回您的计算机(通过上述整个过程)。但是,当外部服务器向您发起连接时,路由器无法知道该连接是针对哪台计算机的,因为计算机 A 和计算机 B 都共享128.120.196.204...的 WAN IP,除非有明确的规则指示路由器将所有最初发往目的端口X的数据包转发到计算机 A,目的端口Y。这称为端口转发. 不幸的是,如果您正在考虑为您的网络应用程序使用端口转发,这是不切实际的,因为您的用户可能不了解如何启用它,并且如果他们认为这是一个安全风险,他们可能不愿意启用它。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 和端口的问题:

所有 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 向计算机 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和改写的端口。如果这就是你想要的(你只要求外部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()andgetPort()可以返回目标或源端口,具体取决于您将其设置为,在客户端(发送)机器上,调用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 遍历的中间人服务器称为“介绍人”。在我的个人项目中,我实际上不需要使用 STUN 来执行 NAT 遍历。我的“介绍人”(介绍客户端 A 和 B 的中间人服务器)是侦听 UDP 数据报的标准服务器。当客户端 A 和 B 都向介绍人注册时,介绍人会读取他们的公共 IP 和端口以及私有 IP(如果他们在 LAN 上)。与所有标准 UDP 数据报一样,从数据报头中读取公共 IP。私有 IP 作为数据报有效负载的一部分写入,引入者只是将其作为有效负载的一部分读取。所以,关于 STUN 的用处,你不需要依赖 STUN 来获取每个客户端的公共 IP 和公共端口——任何连接的套接字都可以告诉你这一点。我'

请详细说明您的障碍:如果您需要有关设计应用程序消息传递协议的最佳实践的建议,以及有关以有序和系统的方式阅读收到的消息的字段的建议(基于您在下面发布的评论),您能否分享您当前的方法?

于 2012-03-12T12:43:57.113 回答
10

您的问题非常广泛-我无法提供示例,但以下链接可能会有所帮助(规格、库、示例等):

于 2012-03-11T16:48:06.733 回答
6

STUN 的基本工作方式如下:防火墙后面的客户端连接到防火墙外的 STUN 服务器。STUN 服务器检查从客户端收到的数据包,并向客户端发送包含客户端 IP 和端口的响应,因为它们出现在 STUN 服务器上。

这就是防火墙后面的客户端发现自己的外部 IP 和端口的方式。据我所知,STUN 服务器通常不会将地址信息从一个客户端传递到另一个客户端。

通常 STUN 用于通过防火墙设置媒体流,当防火墙已经对信令流量开放时 - 例如在 VoIP 中:客户端联系 STUN 服务器以发现其自己的外部 IP 和 UDP 流量端口,然后发送其信令请求( SIP INVITE 或其他)在众所周知的开放端口上发送给其他客户端 - 包括有效负载中的外部 UDP 地址信息(SDP 或其他)。因此,通常需要通过开放端口访问一个客户端,以便为点对点通信发送信号。

于 2012-03-20T11:16:49.747 回答
6

您的问题与 Java 无关。如果您知道如何打开 UDP 连接,那就足够了。阅读以下链接的内容。不要被标题吓到,它也涵盖了 UDP。剩下的只是 Java 编码。

PS:在您的场景中,缺少一个步骤。A 和 B 都必须与 S 有一个开放的连接,因为 S 需要告诉 B A 正试图到达它。如果 B 没有与 S 的开放连接,则 A 和 B 无法开始一起通信。

更新

Jason 的回答包含关于 NAT 遍历的错误和疯狂猜测。应该阅读Saikat Guha 所做的工作 (mpi-sws.org/~francis/imc05-tcpnat.pdf)才能真正理解这件事。维基百科的圆锥分类完全过时且具有误导性。

于 2012-03-15T20:22:02.813 回答