如何以编程方式通过upnp实现双NAT端口转发

Posted

技术标签:

【中文标题】如何以编程方式通过upnp实现双NAT端口转发【英文标题】:How to achieve double NAT port forwarding via upnp programmatically 【发布时间】:2013-09-22 19:41:36 【问题描述】:

我正在尝试使用 UPNP 实现双 NAT。我的电脑连接如下: 互联网 -> 路由器 1 -> 路由器 2-> PC 我已经在路由器 2 上成功完成了端口转发,但在路由器 1 上无法完成。由于来自服务器(在 Internet 上)的数据包无法到达 PC。 经历了很少的文档如何解决,但大多发现手动方式,如桥接网络或放置局域网线。 我宁愿寻找一些使用 UPNP 协议在两个路由器上进行端口转发的递归解决方案。我猜洪流,Skype 也一样。 如何获取与路由器 1 相关的网络接口,然后在该网络上进行端口转发? 任何帮助将不胜感激。

谢谢, 帕万

【问题讨论】:

我认为这是不可能的。在任何情况下,Skype 似乎都不是这样工作的,他们只是(ab)通过 Skype 服务器和 UDP 的无连接性质使用他们的“带外”通信:基本上他们通知双方地址/端口对方正在使用。然后双方尝试相互连接——这种尝试会失败,但它会使本地防火墙认为它应该允许 UDP 流量到这个地址/端口。在这个过程重复足够多的时间后,路由上的所有防火墙应该已经打开了一个洞并且连接尝试成功。 这意味着在 multiNAT 环境中无法使用 UPNP。我猜你说的是UDP打孔。 【参考方案1】:

有一个IGD-PCP IWF specification 试图解决类似的问题,尽管它假设您的“路由器 1”支持 PCP,而不是 UPnP。因此,让我们尝试从纯理论的角度使用两个普通的 UPnP 路由器/NAT 设备来解决这个问题。

根据UPnP Device Architecture version 2.0,UPnP 通信有几个不同的步骤

寻址 发现 说明 控制 事件 演示文稿

寻址对我们来说没什么兴趣,让我们假设到处都有正确的 DHCP 并完成它。 Eventingpresentation 在我们的例子中也几乎没用。所以主要关注的是discoverydescriptioncontrol

发现通过 SSDP 消息交换工作。 SSDP 使用 UDP 进行传输,端口号为 1900(默认)和众所周知的多播地址。

描述从设备在发现阶段提供的 URL 开始,控制点(在我们的例子中是 PC)需要在这个 URL 上发出一个 HTTP GET 请求,这意味着它使用 TCP 作为具有设备 IP 地址的传输协议(单播)。

控件以设备在其描述中提供的 URL 开始,它在 TCP 之上的 HTTP 之上使用 SOAP,这反过来对我们来说也意味着单播 IP。

所以,对于双 NAT,所有这一切意味着在 UPnP 交互的描述和控制步骤中,我们从 PC 到路由器 1 的通信是零问题,因为所有这些只是具有单播 IP 地址的标准 TCP。但是要进入描述步骤,我们需要有一个Router 1的URL,所以让我们仔细看看这个URL是如何正常获取的。

有两种主要的发现机制——广告(当设备周期性地多播一些关于它的信息时)和搜索(当控制点发送多播搜索消息并且设备用单播响应来响应时)。显然,默认情况下,我们在路由器 2 后面的 PC 无法从路由器 1 获得多播广告,而路由器 1 也无法从 PC 获得多播搜索消息,所以我们这里有一个问题,现在的问题是是否有可能没有多播的通信。

幸运的是,同样的架构文档说:

此外,允许控制点将发现消息单播到端口 1900 上的特定 IP 地址或由可选的 SEARCHPORT.UPNP.ORG 标头字段指定的端口(其取代端口 1900 用于此用途),搜索用于该特定 IP 地址的 UPnP 设备或服务。

...

所有设备都应在端口 1900 或 SEARCHPORT.UPNP.ORG 标头字段中指定的端口号(如果提供)上侦听传入的单播搜索消息,并在其任何根设备、嵌入式设备或服务与搜索匹配时做出响应发现消息中的标准。

这意味着如果您知道路由器 1 的 IP 地址(当然是从路由器 2 端),您可以(最重要的是,规范允许)通过单播 UDP 消息与它通信,这也是 NAT-友好,所以不是从路由器 2 后面的 PC 完成的问题。

剩下的唯一事情就是获取路由器 1 的 IP 地址。不幸的是,没有简单的标准方法可以做到这一点,但您至少有两个选择:跟踪路由(以您想要的任何方式)和暴力 IP 扫描(很可能,路由器 1 的潜在 IP 集是有限的)。

现在您可以与路由器 1 进行通信,但在与路由器 1 进行通信时,您仍应始终记住一件小事——在任何内部 UPnP 消息中,您应该使用路由器 2 的 IP 地址(从路由器 1 端看)及其港口。就像在路由器 1 上的 AddPortMapping 操作的 NewInternalClient 参数中一样,您应该使用路由器 2 IP。顺便说一句,这引发了路由器 2 IP 的问题,但您可以通过路由器 2 UPnP ExternalIPAddress 变量 WANIPConnection service 获得该问题(此服务为 required for IGDs to implement)。

所以,总结一下:

这在技术上是可以做到的,尽管我怀疑任何标准库都会为您做到这一点 您需要两件事: Router 2“外部”IP,您可以通过ExternalIPAddress 变量WANIPConnection UPnP 服务获得该IP 路由器 1“内部”IP(来自路由器 2 端),需要跟踪路由或扫描 给定路由器 1 IP,您只需要在发现步骤使用单播消息而不是多播 其他一切都应该正常工作,只是需要注意在 UPnP 消息中使用路由器 2“外部”IP 而不是 PC 的 IP

【讨论】:

【参考方案2】:

使用upnpc cli tool,我已经能够使双 NAT 端口转发工作。

在我的示例中,假设我希望端口 6667 在双 NAT 后转发到我的机器 192.168.50.123。第一层 NAT 的 IP 范围为 192.168.1.1-255,第二层的 IP 范围为 192.168.50.1-255。

在第二层 NAT 内的机器上,像往常一样使用 upnpc 设置端口转发。

$ upnpc -a 192.168.50.123 6667 6667 tcp
...
$ upnpc -l   # this will confirm the rule is in place
...

现在是稍微棘手的部分。我现在向外层 NAT 发出 UPNP 请求。由于通常的 SSDP 发现不起作用,我必须手动指定 XML 根描述 URL。它通常是像http://192.168.1.1:5000/rootDesc.xml 这样的标准,尽管在某些硬件上端口号是随机的。我发现只是端口扫描网关地址最终会发现它。

现在我有了 rootDesc url http://192.168.1.1:5000/rootDesc.xml,然后我向外层 NAT 发出 UPNP 命令:

$ upnpc -u "http://192.168.1.1:5000/rootDesc.xml" -a 192.168.1.XX 6667 6667 tcp
...
$ upnpc -u "http://192.168.1.1:5000/rootDesc.xml" -l   # this will confirm the rule is in place
...

使用 -u 选项而不是发现 rootDesc,它只使用提供的 URL。 地址192.168.1.XX 应该是第一层网络看到的第二层网络的IP 地址。当运行第一组 UPNP 命令时,这将被显示,因为从 NAT 的角度来看,它是“外部”地址。

通过现在的设置,流量将如下所示:

<INTERNET> -->  MY_PUBLIC_IP -> 192.168.1.XX -> 192.168.50.123

【讨论】:

以上是关于如何以编程方式通过upnp实现双NAT端口转发的主要内容,如果未能解决你的问题,请参考以下文章

26-Openwrt 端口转发 dmz upnp

端口转发 (NAT UPNP) 错误

在 Ruby 中通过 UPnP 打开路由器端口

在 Python 中通过 UPnP 转发端口

简单的多人游戏网络,无需专用服务器或需要玩家手动转发端口(无 UPnP)

CentOS实现双网卡网络共享