实现客户端自动绑定路由器外部端口

最近由于工作需要,接触了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开源网站,想要获取更多内容,查看相关网页内容。

posted @ 2013-01-07 17:49  阿Q程序员  阅读(1930)  评论(1编辑  收藏  举报