65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# UPnP 进行 NAT 穿透

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (42投票s)

2008年7月21日

公共领域

5分钟阅读

viewsIcon

553893

downloadIcon

10254

使用 C# UPnP 进行 NAT 穿透,无需任何库。

引言

在编写网络代码时,NAT 可能是一个真正的问题。对于大多数应用程序来说,要求用户向路由器添加端口转发规则不是一个好主意,甚至不可接受,因为用户通常不允许更改路由器的设置,或者不知道如何更改。

幸运的是,有一些方法可以自动化添加端口转发规则的过程。UPnP 是其中一种方法,据我所知,这是最常用的方法。

网络程序的另一个主要问题是,它们似乎没有可靠的方法来查找运行它的计算机的外部 IP 地址。UPnP 部分解决了这个问题,但当然,它并不完全可靠(并非所有路由器都支持它,而支持它的路由器可能出于安全原因关闭了 UPnP)。

如果您曾经尝试使用现有的 UPnP 库,您可能会发现它们大多不起作用,或者没有涵盖 UPnP 的 NAT 方面。这促使我编写了自己的、非常小的 UPnP 库,它涵盖 NAT 方面,甚至不完全涵盖——只包括 UPnP 设备发现、基本端口转发和检索外部 IP 地址。然而,对于许多网络程序来说,这就足够了。

使用代码

在源代码中,您会找到一个名为 NAT 的公共类,其中包含以下公共方法:

  • static bool Discover()
  • static IPAddress GetExternalIP()
  • static void ForwardPort(int port, ProtocolType protocol, string description)
  • static void DeleteForwardingRule(int port, ProtocolType protocol)

请注意,这些方法只进行有限的错误检查,如果不存在 UPnP 设备,或者第一个 UPnP 设备对命令语法过于挑剔,则会失败。在 GetExternalIPForwardPort 工作之前,必须调用 DiscoverDiscover 默认最多需要 3 秒(之后会超时——您可以更改超时值),如果设置的超时值很高,可能需要几分钟,因此对于任何 GUI 应用程序,建议将其放在单独的线程上调用。

要最简单地转发端口,代码如下即可:

UPnP.NAT.Discover();
UPnP.NAT.ForwardPort(12345, ProtocolType.Tcp, "MyApp (TCP)");

实际上,最好检查异常——以及 Discover 返回的值。

UPnP + NAT

对于那些想自己动手的人,或者对此感兴趣的人,以下是我对 UPnP 与 NAT 路由器结合使用的发现。

第一步是找到您感兴趣的 UPnP 设备。显然,最好的方法是使用 SSDP。

使用 SSDP 查找 UPnP 设备归结为通过 UDP 广播一个数据包并检查回复。数据包看起来像这样(或者至少,当它看起来像这样时,它就会起作用)

"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"MAN:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n";

以 ASCII 格式。虽然 Host 是 239.255.255.250,但数据包仍然必须广播到标准的 IPAddress.Broadcast 地址。

检查回复相当直接。您应该只收到一个,但如果您收到了更多,那么正确的回复是可以识别的。来自 NAT 设备的标准回复如下所示:

HTTP/1.1 200 OK
ST:upnp:rootdevice
USN:uuid:00-0C-F6-12-95-D3-FE7BA8C00::upnp:rootdevice
Location:http://192.168.123.254:80/desc.xml
Cache-Control:max-age=1800
Server:IGD-HTTP/1.1 UPnP/1.0 UPnP-Device-Host/1.0
Ext:

ST 应该是 "upnp:rootdevice";如果不是,则不是正确的设备,它本不应该回复,但我们永远不知道网络中会发生什么。

无论如何,我们对 location 感兴趣,因为在该位置会有一个 XML 文件,其中包含有关设备的信息——我们需要这个来找出如何向设备发送命令。

通过一种称为 "service" 的方式,可以向 UPnP 设备发送命令。描述设备的 XML 文件很长,因此只需知道我们感兴趣的服务的 XPath 是

"//tns:service[tns:serviceType=\"urn:schemas-upnp-
     org:service:WANIPConnection:1\"]/tns:controlURL/text()"

由于它使用了命名空间,您必须使用 XmlNamespaceManager,但这又是另一个话题了。

此查询的结果应大致为 "/serv3.xml",再次是一个 XML 文件。请注意,这是一个相对位置,因此您需要将其附加到基本 URL,在本例中为 "http://192.168.123.254:80"。

此文件具有双重目的:首先,其内容告诉您它接受哪些类型的命令以及它将如何回复它们,其次,您将向其发送 SOAP POST 请求。是的,又是 XML。

在此之前,我从未对 XML 文件进行过 HTTP POST 请求,这让我非常惊讶,但显然,它的工作方式应该是这样的。

发送命令就是以正确的方式使用 SOAP。然而,这并不直接——如果您正在使用 WebRequest(就像我一样),那么您不仅要将 Method 设置为 "POST",还要添加一个 header,并更改 content-type

Headers.Add("SOAPACTION", 
  "\"urn:schemas-upnp-org:service:WANIPConnection:1#" + 
  function + "\"");
ContentType = "text/xml; charset=\"utf-8\"";

这里的 function 变量必须与您在内容中发送的函数相同。

这就是全部。有了这些信息,您应该能够实现自己的 UPnP NAT 穿透方法。

关注点

在编写这个小型库的代码时,我完全被该主题缺乏清晰信息所震惊。大多数网站只处理 UPnP 的媒体等方面,或者解释 UPnP NAT 穿透缺乏安全性,而不深入探讨如何实际执行它。当此类信息时,通常是以我至少理解一半的方式编写的。

最终,我使用 WireShark 检查了 µTorrent 的 UPnP 流量,并逆向工程了该过程。我希望本文能为一些人改变现状,使他们不必进行任何逆向工程即可实现 UPnP NAT 穿透。

历史

  • 2008 年 7 月 18 日:编写了 UPnP NAT 库的第一个版本。
  • 2008 年 7 月 22 日:编写了本文的第一个版本。
  • 2008 年 7 月 22 日(当天晚些时候):更新了源代码和文章,使其能与一些更严格的路由器以及端口不是 80 的情况一起工作(感谢 ajiau)。
  • 2008 年 7 月 23 日:更新了源代码,使其能与一些额外的路由器一起工作(感谢 Chris Harper
  • 2009 年 1 月 14 日:更新了源代码和文章,以(希望)能与更多额外的路由器一起工作,感谢过去半年中所有发布反馈的人。
© . All rights reserved.