在现代家庭网络中,经常需要通过互联网服务提供商(ISP)提供的网络连接来访问多个设备。家庭网络路由器通常使用网络地址转换(NAT)技术,将内部的私有IP地址映射到由ISP提供的单个公网IP地址上。路由器的任务是监控哪个内部IP地址发送了请求,然后将来自互联网的响应路由到发起请求的设备。这种机制在设备发起请求并保持连接以获取响应的情况下通常工作得很好。但是,如果互联网上的设备需要访问内部网络上的设备,没有某种映射机制,这将是一个困难的任务。幸运的是,大多数路由器提供了某种端口映射功能,通常在路由器配置选项中的游戏和应用程序部分。静态端口映射要求内部设备的IP地址保持不变(静态),并且需要用户配置路由器,假设用户具备IP地址和端口的知识。另一种选择是让监听来自互联网请求的应用程序命令路由器将连接从路由器的公网IP地址映射到内部IP地址。这个功能通常被称为路由器的UPnP支持。本文介绍了一个简单的控制台实用程序,用于查询路由器当前的映射,清除现有映射,并在路由器上添加新映射。
UPnP(通用即插即用)是一种用于网络设备动态配置和发现的网络协议。它不仅用于在路由器或WIFI设备上映射端口,而且在许多其他设备类型中也很常见。对于这个实用程序的目的,只对UPnP的端口映射方面感兴趣。
在编写这个实用程序时,不想使用自定义代码来解析UPnP与应用程序通信时使用的复杂XML格式(为什么要重新发明轮子呢?)。有趣的是,发现Microsoft Windows通过一个名为“NATUPnP 1.0 Type Library”的COM组件库支持NAT映射。这个库包含在一个名为hnetcfg.dll的DLL中,通常位于\windows\system32文件夹中。根据MSDN文档,这个API自Windows XP以来就存在于Windows中。由于它被打包为组件库,因此C/C++和.NET编程语言都可以访问。
在Windows机器上使用Windows提供的NAT API之前,有一些要求:
第1和第2项通常可以通过Windows防火墙API以编程方式控制。然而,第3项通常由企业管理员决定。由于这个实用程序的目标受众是家庭网络,第3项很少会成为问题。
UpnpStat实用程序是从命令行执行的(可以将其放入.CMD文件中,在登录时执行,或者作为启动其他应用程序的一部分。语法简单,包括以下命令:
add命令需要参数来指定映射的详细信息:
add port protocol description
其中:
例如:要将映射从互联网添加到机器上的端口80(默认Web服务器端口),会输入:
upnpstat add 80 TCP "My Web Server"
该项目设计为使用.NET 4框架的C#控制台应用程序。图1显示了项目的解决方案资源管理器树,注意引用部分包括对NATUPNPLib的引用。这是Windows提供的用于执行NAT相关功能的类型库。
实用程序开始执行时,会解析命令行参数,并在类UpnpHelper中调用相应的方法。不会详细说明命令行解析;它只是一个简单的if语句集合。
当类UpnpHelper被构造时,默认构造函数会创建一个名为NatMgr的UPnPNat库接口的新实例:
class UpnpHelper {
UPnPNAT NatMgr;
public UpnpHelper() {
NatMgr = new UPnPNAT();
}
}
然后使用NatMgr中的方法。例如,UpnpHelper中的ListMappings方法:
class UpnpHelper {
// ...
internal void ListMappings() {
if (NatMgr == null) {
Console.WriteLine("Initialization failed creating Windows UPnPNAT interface.");
return;
}
IStaticPortMappingCollection mappings = NatMgr.StaticPortMappingCollection;
if (mappings == null) {
Console.WriteLine("No mappings found. Do you have a uPnP enabled router as your gateway?");
return;
}
if (mappings.Count == 0) {
Console.WriteLine("Router does not have any active uPnP mappings.");
}
foreach (IStaticPortMapping pm in mappings) {
Console.WriteLine("Description:");
Console.WriteLine(pm.Description);
Console.WriteLine("{0}:{1} ---> {2}:{3} ({4}", pm.ExternalIPAddress, pm.ExternalPort, pm.InternalClient, pm.InternalPort, pm.Protocol);
Console.WriteLine();
}
}
}
核心对象是StaticPortMappingCollection,它代表路由器上的映射集合。使用这个集合,可以删除、添加和迭代映射。这是一个相对简单且直接的接口。查看源代码以了解其他功能的使用方法。