【问题标题】:Java UDP hole punching example - connecting through firewallJava UDP 打孔示例 - 通过防火墙连接
【发布时间】:2012-03-28 05:03:38
【问题描述】:

假设我有两台电脑。

他们通过ice4j 了解彼此的公共和私有 IP。

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

我希望通过 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

【问题讨论】:

  • 设置本地端口并不能保证一旦 NAT 转换为公共 IP,您将使用相同的端口,因此以通用方式解决此问题并不像看起来那么容易。跨度>
  • 请展示一些源代码...你试过什么?什么不工作?
  • @MatBanik 来自我的 POV,您的问题非常广泛...处理您提供的链接中的代码并返回特定问题 - 这将得到更好的答案 IMO...跨度>
  • @MatBanik 那么我发布的链接可能对您的工作有所帮助:-)

标签: java networking hole-punching stun


【解决方案1】:

此示例使用 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_translationhttp://en.wikipedia.org/wiki/IPv4http://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 设备类型为对称)。

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

【讨论】:

  • 这个答案有一些完全的错误和误解:i)TURN“是”一种 NAT 遍历,ii)NAT 遍历不是 TURN 的“替代品”,iii)锥分类有Saikat Guha 的工作很久以前就已经过时了(mpi-sws.org/~francis/imc05-tcpnat.pdf,见表 6,甚至还有一个 RFC,维基百科在这个问题上已经完全过时了)
  • iv) STUN 有几个版本,v) 为证明 STUN 在 NAT 遍历中的冗余性而提出的论点只是揭示了作者在这件事上即兴发挥的程度,并没有彻底理解这个问题.具体来说,STUN 服务器用于执行端口预测并在可能的情况下帮助建立直接 P2P TCP 连接,以及在不可能时需要回退到类似 TURN 的解决方案。这个答案包含许多基于野蛮假设和过时知识的推测理论。对理解 P2P 和 NAT 穿越真的没有帮助。
  • 关于i),作者混淆了NAT穿越和打孔。
  • 对不起,我不是故意发布不正确的信息。我在谷歌上搜索的 NAT 遍历/UDP 打洞文章为我的项目制定了,所以我认为这就是它的工作原理。
  • 多么奇妙而有见地的答案。谢谢。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      更新

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

      【讨论】:

      • 这些来自 Saikat Guha 论文的行对理解 STUN 非常有帮助。在 STUN 中,Alice 向 Bob 发送一个 UDP 数据包。尽管此数据包被 Bob 的 NAT 丢弃,但它会导致 Alice 的 NAT 创建允许 Bob 的响应定向到 Alice 的本地状态。然后 Bob 向 Alice 发送一个 UDP 数据包。 Alice 的 NAT 将其视为第一个数据包流的一部分并将其路由通过,而 Bob 的 NAT 将其视为连接启动并创建本地状态以路由 Alice 的响应。流行的 VoIP 应用程序 Skype 使用了这种方法。
      猜你喜欢
      • 2015-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多