最近由于工作需要,接触了UPNP通用即插即用协议,该协议的目标是使家庭网络(数据共享、通信和娱乐)和公司网络中的各种设备能够相互无缝连接,并简化相关的网络实现。UPnP通过定义和发布基于开放、因特网通讯协议标准的UPnP设备控制协议来实现这一目标。
简单理解,UPnP就是一种动态端口映射。
详细一点说,假设内网某台机器连接的网关设备支持UPnP igd接口并开启了此项功能,那么网关设备就能够响应内网机器的请求,执行一些和网关相关的操作,比如将内网机器的某个端口映射到外网某个固定端口(即NAT)。这样就能够使外网能够直接访问到内网机器的某个端口。
在开始我们介绍主题之前,我们有必要先介绍一些预备知识。
那么,UPnP应用在什么地方呢?
我们比较熟悉的eMule、BitComet等工具,就是用了UPnP,更远一些的应用前景可能在数字家电上。支持UPnP的数字家电,在得到用户授权后,能够自动到家中的网关上开启端口映射,这样用户就可以在外网对家中的家电进行控制。
下面简单介绍一下UPnP端口映射的过程和简单原理:
1、寻址;机获取一个本地IP地址
2、发现;即搜索网络中的UPnP设备
3、描述;即得到UPnP设备属性和描述信息
4、控制;即对UPnP设备发送控制命令(如增删端口和映射)或查询属性
5、事件触发;即在设备一些属性变化时,如果控制点订阅了它的事件通知,则它会发送相应的通知给控制点。
6、展示;
相关扩展信息查阅:
http://www.microsoft.com/china/windowsxp/pro/techinfo/planning/upnp/howitworks.mspx
下面,我们开始用NAT-PMP(Network Address Translation Port Mapping Protocol)协议来实现外网IP的获取。通过对libupnp和miniupnp两个项目的了解,感觉libupnp使用较为复杂,对于一般程序开发而言,miniupnp已经能够满足我们的基本需求,且函数接口和操作步骤简单,易于实现。miniupnp中libatpmp库基于异步事件驱动机制和非阻塞套接字上实现异步操作,能够方便的集成到任何基于事件驱动的。
在你的程序中使用libnatpmp库是如此的简单,所有的API接口都定义在了natpmp.h头文件中,你只需要如下几个简单操作:
1、定义一个natpmp_t类型的变量,同时调用initnatpmp()函数;
2、调用sendpublicaddressrequest()或者sendnewportmappingrequest函数,发送获取公网IP或者设置新端口映射请求;
3、如果你正在等待通过系统调用在指定套接字(在natpmp_t对象的s域中)上准备去读,你可以使用getnatpmprequesttimeout()函数获取在readnatpmpresponseorretry()函数上执行的时间。
4、释放所有资源,然后调用closenatpmp()函数。
下面给出两个简单的代码:
第一个代码非常简单,只是进行了一个重定向操作,然后返回
1: void redirect(uint16_t privateport, uint16_t publicport)
2:
3: {
4:
5: int r;
6:
7: natpmp_t natpmp;
8:
9: natpmpresp_t response;
10:
11: initnatpmp(&natpmp);
12:
13: sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, privateport, publicport, 3600);
14:
15: do {
16:
17: fd_set fds;
18:
19: struct timeval timeout;
20:
21: FD_ZERO(&fds);
22:
23: FD_SET(natpmp.s, &fds);
24:
25: getnatpmprequesttimeout(&natpmp, &timeout);
26:
27: select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
28:
29: r = readnatpmpresponseorretry(&natpmp, &response);
30:
31: } while(r==NATPMP_TRYAGAIN);
32:
33: printf("mapped public port %hu to localport %hu liftime %u\n",
34:
35: response.newportmapping.mappedpublicport,
36:
37: response.newportmapping.privateport,
38:
39: response.newportmapping.lifetime);
40:
41: closenatpmp(&natpmp);
42:
43: }
第二个代码稍微复杂,搭建了一个P2P程序初始化的一个框架,在框架中我们可以继续实现自己的代码,程序尝试获取公网IP同时添加一条新的端口映射规则,natpmpstate作为程序执行结果被返回,它有两个可选值:Sdone或者Serror.
1: natpmpstate initmyP2P()
2:
3: {
4:
5: natpmp_t natpmp;
6:
7: natpmpresp_t response;
8:
9: enum { Sinit=0, Ssendpub, Srecvpub, Ssendmap, Srecvmap, Sdone, Serror=1000 } natpmpstate = Sinit;
10:
11: int r;
12:
13: [...]
14:
15: if(initnatpmp(&natpmp)<0)
16:
17: natpmpstate = Serror;
18:
19: else
20:
21: natpmpstate = Ssendpub;
22:
23: [...]
24:
25: while(!finished_all_init_stuff) {
26:
27: [...]
28:
29: other init stuff :)
30:
31: [...]
32:
33: switch(natpmpstate) {
34:
35: case Ssendpub:
36:
37: if(sendpublicaddressrequest(&natpmp)<0);
38:
39: natpmpstate = Serror;
40:
41: else
42:
43: natpmpstate = Srecvpub;
44:
45: break;
46:
47: case Srecvpub:
48:
49: r = readnatpmpresponseorretry(&natpmp, &response);
50:
51: if(r<0 && r!=NATPMP_TRYAGAIN)
52:
53: natpmpstate = Serror;
54:
55: else if(r!=NATPMP_TRYAGAIN) {
56:
57: copy(publicaddress, response.publicaddress.addr);
58:
59: natpmpstate = Ssendmap;
60:
61: }
62:
63: break;
64:
65: case Ssendmap:
66:
67: if(sendnewportmappingrequest(&natpmp, protocol, privateport, publicport, lifetime)<0);
68:
69: natpmpstate = Serror;
70:
71: else
72:
73: natpmpstate = Srecvmap;
74:
75: break;
76:
77: case Srecvmap:
78:
79: r = readnatpmpresponseorretry(&natpmp, &response);
80:
81: if(r<0 && r!=NATPMP_TRYAGAIN)
82:
83: natpmpstate = Serror;
84:
85: else if(r!=NATPMP_TRYAGAIN) {
86:
87: copy(publicport, response.newportmapping.mappedpublicport);
88:
89: copy(privateport, response.newportmapping.privateport);
90:
91: copy(mappinglifetime, response.newportmapping.lifetime);
92:
93: natpmpclose(&natpmp);
94:
95: natpmpstate = Sdone;
96:
97: }
98:
99: break;
100:
101: }
102:
103: [...]
104:
105: }
106:
107: [...]
108:
109: }
以上内容来自http://miniupnp.free.fr/minissdpd.html开源网站,想要获取更多内容,查看相关网页内容。