upnp

自己动手实现UPnP进行端口映射的经过

    从接到任务,到实现了 UPnP 在家用路由器上进行端口映射的时间总共花费了 1个半月,下面大概讲讲从资料搜集到设计实现的经过,好给有同样需求的人一些线索(不论及具体实现和代码)。
    所谓 UPnP ,就是“通用的即插即用” ,注意是通用的,虽然很容易和 Windows 的即插即用混淆,但这肯定不是微软的专利!现在大部分的路由器都支持这个功能,只是默认情况下没有打开而已(基于安全考虑)。请管理员手动打开这个支持选项。

    这是用来干什么用的?如果我们要写 P2P 软件,那就用的着了,电骡不是有所谓的 LowID 和 highID 吗? 为了提高自己的共享能力(我为别人共享,别人也为我共享),我们(软件)要使用公网 IP 地址监听和建立连接,但是我们(软件)不是路由器,如何监听? 只好请路由器帮我们做一个端口映射,然后我们(软件)在内网监听,效果跟在公网上监听一样,也就是所谓 电骡的 HighID 了。 现在越来越多的用户都是内网用户的上网形式(NAT),如网吧。能够把自己的 LowID 提升为 HighID ,那么肯定会有更多的备选数据源啊,这样下载就被加速了!
不说那么多废话了,如何开始?所有资料都是在http://upnp.org/ 上,着重看 《Internet Gateway Device (IGD) V 1.0》文档就行,其他都不是我们所关注的。这些文档的打包里,实际上对我真正有用的是一个叫做 《UPnP_IGD_WANIPConnection 1.0.pdf》 的文档,其它的内容实在太多了,我偷懒都没看。不过就算这篇文档也不用急着看,先下载保存好,以后会用的到的。
后续的开发需要对报文抓包分析的,建议对自己用的顺手的网络报文分析工具先熟悉一下,我用的是 wireShark (前身是 Ethereal ),这个很重要。
碰 巧是,我得知,原来微软有那么一个 COM 组件是支持 UPnP 功能的,当时一个想法就是想直接用微软的那个 API 实现了功能就是。上网搜,打开 MSDN 搜,终于给我发现,原来 Platform 2003 SDK 里面有个例子,是专门用来执行 upnp 功能的,虽然不是直接用来做 端口映射的,但已经非常具备参考意义了。 下面那个就是我安装 Paltform 2003 后的 Sample 目录:  "D:/ProgramFiles/PaltformSDK/Samples/netds/upnp/GenericUCP" 请调整好您的 VC6 ,加载这个工程吧。
    既然编译好了这个工程,那就要找一个试验的环境了,我在网络里加了一个家庭网关(小型路由器),并打开了它的 uPnP 功能,使用该工具体验了一下如何找到一个 uPnP 设备(及指定service),并获取它的状态参数,向它发出 action 指令! 这时候,之前让下载的 .pdf 文档终于排上用场了。打开他,发现他里面有一些参数(STATE VARIABLES)可以 Get 、 也有某些 action 可以执行(当然要带上指定参数),那篇 pdf 文档里面都说的非常清楚。不说那么多,先执行最简单的 action | (GetExternalIPAddress),参数全为空就行了。
经过测试,抛开STATE VARIABLES 不用(因为要向网关注册我所监听的 event 地址的,而 action 则不用),只用 action 实现了自动端口映射,要用到的 action 有如下列几个(参数不列出了,可以查文档得知):
 ● GetExternalIPAddress
 ● GetGenericPortMappingEntry
 ● AddPortMapping
 ● DeletePortMapping

    好了,这几个足已。这就是 uPnP 模块的雏形了,不过这还不能直接拿来用的。把里面能抄的东西都搬到自己的终端软件当中,果然在简单组网的环境里还非常好用,不过,拿到广州电信研究院进行 复杂组网测试的时候,灾难发生了,网络里竟然有超过一个小型网关!最后,软件竟然在别人的小型网关里建立端口映射! 完蛋了,怎么会这样呢? 抓包分析,错误定位,这样一步一步下来,也终于搞清楚了 uPnP 的整个交互流程,也萌生了自己实现 uPnP 功能模块的念头,实际上就是特定格式的 xml 交互嘛(http方式)。进行了完备的抓包分析之后,搞清楚所执行的 action 的特定 xml 命令的格式。我重新开始了编程,一个星期后重写模块完毕,抛弃了微软那套操作 uPnP 的 API 以及烦人的头文件包含,并且也不用担心 Win2000 下不支持该 COM 组件了。
下面简单讲讲整个 uPnP 的交互流程,如果有兴趣的就姑且作为一个开发过程的线索吧,具体报文内容请自行抓包GenericUCP 例程和家庭网关的交互则一目了然 :

  ① 一开始,程序随便绑定一个 UDP 端口,向组播地址 "239.255.255.250:1900" 发送探测报文(查找的设备类型是WANConnectionDevice:1 ), 并在超时时间内 recvfrom( ) 等待回应报文。如果网络上的设备有满足搜索条件的,都会直接回应 UDP 报文到该 socket 处。
  // -----------------  UDP 交互结束  -----------------------

  ② 如果回应报文是正确的,那么从中应该可以看到对端 uPnP 设备监听的 Location 了, 一个 http 地址,向这个 http 地址发出 TCP 的 http GET 请求,得到一个 xml 说明页 (可直接用 Iexplore.exe 打开来看),在里面确认到有没有 "WANConnectionDevice:1" 这个子设备,其下应该会有 "WANIPConnection:1" 的这个 service,把其中的 <controlURL> 取出来就行了。

  ③ 在取得 <controlURL> 以后,要做的事情就是向这个 http 地址发送 POST 请求,带上相应 action 的控制命令 xml 就行了,具体可以用GenericUCP 做一遍,然后抓包后原样照抄并做一些参数替换即可。

  - ④ – 可选的,如果想从 uPnP 设备那里获得一些参数变化的通告(比方有第三者新增或者删掉了某端口映射,引起 "PortMappingNumberOfEntries" 参数的变化),可以向(从 Location 处 GET 回来的说明页中的) <eventSubURL> 发出注册请求,说明自己正在监听的 http 地址,这样的话最新参数变化后 uPnP 设备都会主动发到这个地址来,不过别忘了在程序退出前取消掉注册哦。(为了减少代码量,我没做这方面的支持)。 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章