peakzhang 发表于 2007-12-14 00:00:59

P2P端口映射 UPnP功能和使用详解

在网上看了很多关于如何打开UPnP功能的文章,发现竟然没有一篇文章能把整个UPnP的设置过程介绍全的,都是只讲到一部分。所以决定写篇文章,至少把设置UPnP的整体思路理一下,因为涉及到不同的操作系统以及不同型号的ADSL Modem,所以此文也不可能面面俱到,但至少提供一个较为完整的思路。因水平有限,不到之处还请高手指点。   一、UPnP的概念:
    以下是微软官方网站对UPnP的解释:
    问:什么是 UPnP?
    答:通用即插即用 (UPnP) 是一种用于 PC 机和智能设备(或仪器)的常见对等网络连接的体系结构,尤其是在家庭中。UPnP 以 Internet 标准和技术(例如 TCP/IP、HTTP 和 XML)为基础,使这样的设备彼此可自动连接和协同工作,从而使网络(尤其是家庭网络)对更多的人成为可能。
    问:UPnP 对消费者意味着什么?
    答:简单、更多选择和更新颖的体验。包含通用即插即用技术的网络产品只需实际连到网络上,即可开始正常工作。实际上,UPnP 可以和任何网络媒体技术(有线或无线)协同使用。举例来说,这包括:Category 5 以太网电缆、Wi-Fi 或 802.11B 无线网络、IEEE 1394("Firewire")、电话线网络或电源线网络。当这些设备与 PC 互连时,用户即可充分利用各种具有创新性的服务和应用程序。
    以下是BC官方网站对UPnP的解释:
    UPnP(Universal Plug and Play),通用即插即用,是一组协议的统称,不能简单理解为UPnP=“自动端口映射”。在BitComet下载中,UPnP包含了2层意思:
    1、对于一台内网电脑,BitComet的UPnP功能可以使网关或路由器的NAT模块做自动端口映射,将BitComet监听的端口从网关或路由器映射到内网电脑上。
    2、网关或路由器的网络防火墙模块开始对Internet上其他电脑开放这个端口。
    我倒是觉得微软的解释过于理论化,适合写入牛津大词典;而BC官方网站的解释过于含蓄晦涩。其实对于一般的使用者来讲,简单的把UPnP理解为自动端口映射就可以了。它就是一种基于TCP/IP协议的,针对设备彼此间的通讯而制订的新的Internet协议,目的就是希望未来所有联入Internet中的设备能够不受网关阻碍的相互通信。
    二、哪些用户需要用UPnP功能?
    1、只有在需要使用一些支持UPnP功能的P2P软件的时候,如BT、电骡eMule、MSN等,我们才需要考虑UPnP这个东东。如果你根本就不用这些软件,仅仅是上网浏览的话,下文就没必要看了;
    2、如果你需要使用这些P2P软件,但你是外网用户,那么下文也可以不看了,因为你不需要做什么UPnP就可以正常使用这些P2P软件了;
    3、如果你是内网用户,但你已经手动为这些P2P软件进行了端口映射,如在使用BC下载时,在“用户列表”中已经看到“远程”,或者是使用电骡eMule连接服务器成功后,已经显示为高ID,那么下文也可以不看了。
    但需要注意的是,手动做的端口映射只是针对某个P2P软件起作用,如果再使用新的P2P软件的话,仍然需要针对新的P2P软件做相应的端口映射才可以;
    4、如果你是内网用户,需要使用这些P2P软件,而且并未进行手动端口映射,比如在使用BC进行下载时,“用户列表”中只有“本地”而没有“远程”,在使用电骡eMule的时候,显示的也是低ID,那么此时我们才需要考虑端口映射的问题!
    这时我们可以有两种选择:
    1、进行手动端口映射。
    2、打开UPnP功能,进行自动端口映射,也就是我们下面所讲的内容;
    小结:UPnP自动端口映射的目的:
    以BC为例,手动端口映射和自动UPnP端口映射都是为了获得远程连接,因此凡是在“用户列表”里看见了“远程”的用户,都不需要进行端口映射或是UPnP!UPnP和端口映射只需要取其一,他们相当于达到目的的2种不同的方法而已。
    因此如果你的系统或者硬件不支持UPnP功能,大可不必伤心,按照第3点的链接文章中的方法手动做端口映射就可以了,效果是一样的;
    三、实现UPnP必须满足哪些条件:
    必须同时满足3个条件:
    1、Modem必须支持UPnP功能:是否具备此功能可查阅说明书或者直接咨询厂家。一般来讲,Modem还必须同时支持路由功能,除非你配备了单独的路由器;
    2、操作系统的支持:文章开头提到的那篇介绍UPnP的文章中,提到只有windows xp系统才支持UPnP功能,但微软的官方网站声称从Windows Me开始就已经支持UPnP功能了。但Windows Me这个操作系统我也没有用过,大家可以自行测试;
    3、软件必须支持UPnP功能:如BC、电骡eMule、MSN等软件都支持UPnP功能;
    顺便提一下,目前的几款视讯聊天软件各有其特殊性,象Netmeeting、QQ等就不支持UPnP功能,文章的最后会详细阐述一下这几款视频聊天软件的特殊性,此处不深入探讨,一笔带过;
    注意:以上3个条件必须同时满足,缺一不可,否则你只能考虑进行手动端口映射了;
    四、如何打开UPnP功能
    如果已经满足了上述的3个条件,那么我们就一步一步的讲解一下到底如何才能打开UPnP功能;
    1、在Modem中打开UPnP功能。
    不同型号的Modem设置界面和方法略有不同,如有些是在下拉菜单中选择Enable,但基本的原理都是一样的;
    有些文章提到,此时要把所有桥接的eoa连接都删除掉,我倒是认为大可不必如此。因为目前只有你的PPPOE连接是有效的,其它的几个eoa所对应的VPI和VCI根本就是无效的,所以没有必要删除。但是否有些型号的Modem会比较特殊也很难讲,因此建议此时暂不删除,把全部设置进行完后,如果还是没有打开UPnP,再尝试把eoa删除掉;
    当然,设置完后,一定要保存并重启Modem;
    2、在操作系统中打开UPnP功能:
    如果你使用的是XP SP2系统,则首先进入:控制面板->添加或删除程序->添加/删除windows组件中,在“网络服务”中勾选“UPnP用户界面”。
    确定后,系统会自动安装相应的组件,可能会提示你插入安装光盘,总之按照提示操作完成即可;
    接着打开Windows自带的防火墙,在“例外”选项卡中勾选“UPnP框架”。
    其实有个更加简单的方法可以同时完成以上两步:双击桌面上的网上邻居(注意是鼠标左键双击,不是右键查看属性),然后点击“显示联网的UPnP设备的图标”,系统会自动安装UPnP组件以及在防火墙中打开UPnP框架,实际上就是一次性完成上面两步的工作;
    如果你使用的是XP SP1系统,那么在“windows组件”中显示的是“通用即插即用”,而不是“UPnP用户界面”,选择此项即可。
    而且XP SP1系统的防火墙并没有UPnP框架的选项,需要手动进行端口添加,另一教程对此做了阐述,现引用过来:
    「请在防火墙设置中,点“高级”,然后自行添加如下两个端口:TCP端口类型,端口号为:2869,UDP端口类型,端口号为:1900 .由于你使用了NAT网关,所以你应该设置的是您连接到该网关的网卡的防火墙。而且网关内部均为内网,所以开启这两个端口,不会对系统造成安全隐患(除非你的NAT网关被绕过,否则外部连接无法检测到该端口)。」
    以上的防火墙设置只是针对windows自带的防火墙,如果你安装了其它的防火墙,必须在该防火墙中打开UPnP框架;
    3、在windows中打开相应的UPnP服务:
    进入“控制面板->管理工具->服务”,找到SSDP Discovery Service和Universal Plug and Play Device Host两项服务。
    右击相应的服务项,选择属性,启动这两项服务。
    做完以上工作后,如果操作正确,我们就可以在“网络连接”中看到多了一项网关,这表明添加UPnP已经成功;
    4、打开P2P软件中的UPnP功能:
    以BC和电骡eMule为例,相应的设置选项。
    到此为止,我们打开UPnP的工作才真正结束。
    以BC为例,成功添加UPnP功能后,在“全局日志”中我们会看到类似下面的几行:
    Windows XP UPnP Status: Found WAN Connection Device [http://www.linksys.com/] Windows XP UPnP Status: WAN IP: 218.30.*.* Windows XP UPnP Status: Port Mapping Existed!
    此时我们用BC进行下载,如果用户列表中有“远程”,或者用电骡eMule连接服务器后显示为高ID,那么就大功告成了!
    五、对几款视频聊天软件的简单对比说明:
    目前常用的视频聊天软件主要有MSN、Netmeeting和QQ等,这3款软件中只有MSN支持UPnP功能,而且发现在MSN的选项中并没有设置UPnP功能的选项,也就是说MSN始终是默认打开UPnP功能的。另外,MSN似乎也没有象BC或者电骡eMule那样提供可以手动进行端口映射的端口号,因此只要没有打开UPnP功能,MSN的功能就会受限,比如不能进行语音通信等;
    而Netmeeting虽然不支持UPnP功能,但是却提供了可以进行手动进行端口映射的端口号,如果你是内网用户,只要手动进行1503和1720两个端口的映射即可正常使用所有音视频功能;
    QQ是用UDP的方式,通过UDP服务器来实现音视频以及文件的传输,跟UPnP没有什么关系,所以无论内网还是外网,使用QQ都畅行无阻,只是传输的速率要慢些;
    说实话,本人平时也不常上网聊天,所以对这些聊天软件了解也不深,如果有说得不对的地方,欢迎高手指点。

peakzhang 发表于 2007-12-14 00:01:26


UPNP.h

#ifndef MYUPNP_H_

#pragma once

typedef unsigned long ulong;

class MyUPnP
{
public:
typedef enum{
UNAT_OK,      // Successfull
UNAT_ERROR,      // Error, use GetLastError() to get an error description
UNAT_NOT_OWNED_PORTMAPPING,// Error, you are trying to remove a port mapping not owned by this class
UNAT_EXTERNAL_PORT_IN_USE,// Error, you are trying to add a port mapping with an external port in use
UNAT_NOT_IN_LAN   // Error, you aren't in a LAN -> no router or firewall
} UPNPNAT_RETURN;

typedef enum{
UNAT_TCP,      // TCP Protocol
UNAT_UDP      // UDP Protocol
} UPNPNAT_PROTOCOL;

typedef struct{
WORD internalPort;    // Port mapping internal port
WORD externalPort;    // Port mapping external port
UPNPNAT_PROTOCOL protocol;// Protocol-> TCP (UPNPNAT_PROTOCOL:UNAT_TCP) || UDP (UPNPNAT_PROTOCOL:UNAT_UDP)
CString description;   // Port mapping description
} UPNPNAT_MAPPING;

MyUPnP();
~MyUPnP();

UPNPNAT_RETURN AddNATPortMapping(UPNPNAT_MAPPING *mapping, bool tryRandom = false);
UPNPNAT_RETURN RemoveNATPortMapping(UPNPNAT_MAPPING mapping, bool removeFromList = true);
void clearNATPortMapping();
bool RemoveSpecifiedPort(WORD port,UPNPNAT_PROTOCOL protocol);

CStringGetLastError();
CStringGetLocalIPStr();
DWORDGetLocalIP();
boolIsLANIP(DWORD nIP);

protected:
voidInitLocalIP();
voidSetLastError(CString error);

bool addPortmap(int eport, int iport, const CString& iclient,
      const CString& descri, const CString& type);
bool deletePortmap(int eport, const CString& type);

boolisComplete() const { return !m_controlurl.IsEmpty(); }

boolSearch(int version=1);
boolGetDescription();
CStringGetProperty(const CString& name, CString& response);
boolInvokeCommand(const CString& name, const CString& args);

boolValid()const{return (!m_name.IsEmpty()&&!m_description.IsEmpty());}
boolInternalSearch(int version);
CStringm_devicename;
CStringm_name;
CStringm_description;
CStringm_baseurl;
CStringm_controlurl;
CStringm_friendlyname;
CStringm_modelname;
int   m_version;

private:
CList<UPNPNAT_MAPPING, UPNPNAT_MAPPING> m_Mappings;

CStringm_slocalIP;
CStringm_slastError;
DWORDm_uLocalIP;

boolisSearched;
};
#endif


peakzhang 发表于 2007-12-14 00:02:05

UPNP.cpp

#include "stdafx.h"

#include "upnp.h"

#define UPNPPORTMAP0   _T("WANIPConnection")
#define UPNPPORTMAP1   _T("WANPPPConnection")
#define UPNPGETEXTERNALIP _T("GetExternalIPAddress"),_T("NewExternalIPAddress")
#define UPNPADDPORTMAP _T("AddPortMapping")
#define UPNPDELPORTMAP _T("DeletePortMapping")

static const ulong UPNPADDR = 0xFAFFFFEF;
static const int UPNPPORT = 1900;
static const CString URNPREFIX = _T("urn:schemas-upnp-org:");

const CString getString(int i)
{
CString s;

s.Format(_T("%d"), i);

return s;
}

const CString GetArgString(const CString& name, const CString& value)
{
return _T("<") + name + _T(">") + value + _T("</") + name + _T(">");
}

const CString GetArgString(const CString& name, int value)
{
return _T("<") + name + _T(">") + getString(value) + _T("</") + name + _T(">");
}

bool SOAP_action(CString addr, uint16 port, const CString request, CString &response)
{
char buffer;

const CStringA sa(request);
int length = sa.GetLength();
strcpy(buffer, (const char*)sa);

uint32 ip = inet_addr(CStringA(addr));
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr.S_un.S_addr = ip;
int s = socket(AF_INET, SOCK_STREAM, 0);
u_long lv = 1;
ioctlsocket(s, FIONBIO, &lv);
connect(s, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
Sleep(50);
/*int n =*/ send(s, buffer, length, 0);
Sleep(500);
int rlen = recv(s, buffer, sizeof(buffer), 0);
closesocket(s);
if (rlen == SOCKET_ERROR) return false;
if (!rlen) return false;

response = CString(CStringA(buffer, rlen));

return true;
}

int SSDP_sendRequest(int s, uint32 ip, uint16 port, const CString& request)
{
char buffer;

const CStringA sa(request);
int length = sa.GetLength();
strcpy(buffer, (const char*)sa);

struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(port);
sockaddr.sin_addr.S_un.S_addr = ip;

return sendto(s, buffer, length, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
}

bool parseHTTPResponse(const CString& response, CString& result)
{
int pos = 0;

CString status = response.Tokenize(_T("\r\n"), pos);

result = response;
result.Delete(0, pos);

pos = 0;
status.Tokenize(_T(" "), pos);
status = status.Tokenize(_T(" "), pos);
if (status.IsEmpty() || status!='2') return false;
return true;
}

const CString getProperty(const CString& all, const CString& name)
{
CString startTag = '<' + name + '>';
CString endTag = _T("</") + name + '>';
CString property;

int posStart = all.Find(startTag);
if (posStart<0) return CString();

int posEnd = all.Find(endTag, posStart);
if (posStart>=posEnd) return CString();

return all.Mid(posStart + startTag.GetLength(), posEnd - posStart - startTag.GetLength());
}

MyUPnP::MyUPnP()
: m_version(1)
{
m_uLocalIP = 0;
isSearched = false;
}

MyUPnP::~MyUPnP()
{
UPNPNAT_MAPPING search;
POSITION pos = m_Mappings.GetHeadPosition();
while(pos){
search = m_Mappings.GetNext(pos);
RemoveNATPortMapping(search, false);
}

m_Mappings.RemoveAll();
}


bool MyUPnP::InternalSearch(int version)
{
if(version<=0)version = 1;
m_version = version;

#define NUMBEROFDEVICES 2
CString devices[] = {
{UPNPPORTMAP1, _T("service")},
{UPNPPORTMAP0, _T("service")},
{_T("InternetGatewayDevice"), _T("device")},
};

int s = socket(AF_INET, SOCK_DGRAM, 0);
u_long lv = 1;
ioctlsocket(s, FIONBIO, &lv);

int rlen = 0;
for (int i=0; rlen<=0 && i<500; i++) {
if (!(i%100)) {
   for (int i=0; i<NUMBEROFDEVICES; i++) {
    m_name.Format(_T("%s%s:%s:%d"), URNPREFIX, devices, devices, version);
    CString request;
    request.Format(_T("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: %d\r\nST: %s\r\n\r\n"),
   6, m_name);
    SSDP_sendRequest(s, UPNPADDR, UPNPPORT, request);
   }
}

Sleep(10);

char buffer;
rlen = recv(s, buffer, sizeof(buffer), 0);
if (rlen <= 0) continue;
closesocket(s);

CString response = CString(CStringA(buffer, rlen));
CString result;
if (!parseHTTPResponse(response, result)) return false;

for (int d=0; d<NUMBEROFDEVICES; d++) {
   m_name.Format(_T("%s%s:%s:%d"), URNPREFIX, devices, devices, version);
   if (result.Find(m_name) >= 0) {
    for (int pos = 0;;) {
   CString line = result.Tokenize(_T("\r\n"), pos);
   if (line.IsEmpty()) return false;
   CString name = line.Mid(0, 9);
   name.MakeUpper();
   if (name == _T("LOCATION:")) {
      line.Delete(0, 9);
      m_description = line;
      m_description.Trim();
      return GetDescription();
   }
    }
   }
}
}
closesocket(s);

return false;
}

bool MyUPnP::Search(int version)
{
if (isSearched) return isComplete();

isSearched = true;

return InternalSearch(version);
}

static CString NGetAddressFromUrl(const CString& str, CString& post, CString& host, int& port)
{
CString s = str;

post = _T("");
host = post;
port = 0;
int pos = s.Find(_T("://"));
if (!pos) return CString();
s.Delete(0, pos + 3);

pos = s.Find('/');
if (!pos) {
host = s;
s = _T("");
} else {
host = s.Mid(0, pos);
s.Delete(0, pos);
}

if (s.IsEmpty()) {
post = _T("");
} else {
post = s;
}

pos = 0;
CString addr = host.Tokenize(_T(":"), pos);
s = host.Tokenize(_T(":"), pos);
if (s.IsEmpty()) {
port = 80;
} else {
port = _tstoi(s);
}

return addr;
}

bool MyUPnP::GetDescription()
{
if(!Valid())return false;
CString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_description, post, host, port);
if(addr.IsEmpty())return false;
CString request = CString(_T("GET ")) + post + _T(" HTTP/1.1\r\nHOST: ") + host + _T("\r\nACCEPT-LANGUAGE: en\r\n\r\n");
CString response;
if (!SOAP_action(addr, (uint16)port, request, response)) return false;
CString result;
if (!parseHTTPResponse(response, result)) return false;

m_friendlyname = getProperty(result, _T("friendlyName"));
m_modelname = getProperty(result, _T("modelName"));
m_baseurl = getProperty(result, _T("URLBase"));
if(m_baseurl.IsEmpty())m_baseurl = CString(_T("http://")) + host + _T("/");
if(m_baseurl!='/')m_baseurl += _T("/");

CString serviceType = _T("<serviceType>") + m_name + _T("</serviceType>");
int pos = result.Find(serviceType);
if (pos >= 0) {
result.Delete(0, pos + serviceType.GetLength());
pos = result.Find(_T("</service>"));
if (pos >= 0) {
   result = result.Mid(0, pos);
   m_controlurl = getProperty(result, _T("controlURL"));
   if (!m_controlurl.IsEmpty() && m_controlurl == '/') {
    m_controlurl = m_baseurl + m_controlurl.Mid(1);
   }
}
}

return isComplete();
}

CString MyUPnP::GetProperty(const CString& name, CString& response)
{
if (!isComplete())return CString();
CString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_controlurl, post, host, port);
if(addr.IsEmpty())return CString();
CString cnt;
CString psr;
cnt.Append(_T("<s:Envelope\r\n    xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\r\n    "));
cnt.Append(_T("s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n<s:Body>\r\n    <u:"));
cnt.Append(name);
cnt.Append(_T(" xmlns:u=\""));
cnt.Append(m_name);
cnt.Append(_T("\">\r\n    </u:"));
cnt.Append(name);
cnt.Append(_T(">\r\n</s:Body>\r\n</s:Envelope>\r\n\r\n"));
psr.Append(_T("POST "));
psr.Append(post);
psr.Append(_T(" HTTP/1.1\r\nHOST: "));
psr.Append(host);
psr.Append(_T("\r\nContent-Length: "));
psr.Append(getString(CStringA(cnt).GetLength()));
psr.Append(_T("\r\nContent-Type: text/xml; charset=\"utf-8\"\r\nSOAPAction: \""));
psr.Append(m_name);
psr.Append(_T("#"));
psr.Append(name);
psr.Append(_T("\"\r\n\r\n"));
psr.Append(cnt);

CString request = psr;
if (!SOAP_action(addr, (uint16)port, request, response)) return CString();
CString result;
if (!parseHTTPResponse(response, result)) return CString();

return getProperty(result, response);
}

bool MyUPnP::InvokeCommand(const CString& name, const CString& args)
{
if(!isComplete())return false;
CString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_controlurl, post, host, port);
if(addr.IsEmpty())return false;
CString cnt;
CString psr;
cnt.Append(_T("<?xml version=\"1.0\"?><s:Envelope\r\n    xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\r\n    "));
cnt.Append(_T("s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n<s:Body>\r\n    <u:"));
cnt.Append(name);
cnt.Append(_T(" xmlns:u=\""));
cnt.Append(m_name);
cnt.Append(_T("\">\r\n"));
cnt.Append(args);
cnt.Append(_T("    </u:"));
cnt.Append(name);
cnt.Append(_T(">\r\n</s:Body>\r\n</s:Envelope>\r\n\r\n"));
psr.Append(_T("POST "));
psr.Append(post);
psr.Append(_T(" HTTP/1.1\r\nHOST: "));
psr.Append(host);
psr.Append(_T("\r\nContent-Length: "));
psr.Append(getString(CStringA(cnt).GetLength()));
psr.Append(_T("\r\nContent-Type: text/xml; charset=\"utf-8\"\r\nSOAPAction: \""));
psr.Append(m_name);
psr.Append(_T("#"));
psr.Append(name);
psr.Append(_T("\"\r\n\r\n"));
psr.Append(cnt);

CString response;
CString request = psr;
if (!SOAP_action(addr, (uint16)port, request, response)) return false;
CString result;
if (!parseHTTPResponse(response, result)) return false;

return true;
}

bool MyUPnP::addPortmap(int eport, int iport, const CString& iclient, const CString& descri, const CString& type)
{
CString args;

args.Empty();
args.Append(GetArgString(_T("NewRemoteHost"), _T("")));
args.Append(GetArgString(_T("NewExternalPort"), eport));
args.Append(GetArgString(_T("NewProtocol"), type));
args.Append(GetArgString(_T("NewInternalPort"), iport));
args.Append(GetArgString(_T("NewInternalClient"), iclient));
args.Append(GetArgString(_T("NewEnabled"), _T("1")));
args.Append(GetArgString(_T("NewPortMappingDescription"), descri));
args.Append(GetArgString(_T("NewLeaseDuration"), 0));

return InvokeCommand(UPNPADDPORTMAP, args);
}

bool MyUPnP::deletePortmap(int eport, const CString& type)
{
CString args;

args.Empty();
args.Append(GetArgString(_T("NewRemoteHost"), _T("")));
args.Append(GetArgString(_T("NewExternalPort"), eport));
args.Append(GetArgString(_T("NewProtocol"), type));

return InvokeCommand(UPNPDELPORTMAP, args);
}

/////////////////////////////////////////////////////////////////////////////////
// Adds a NAT Port Mapping
// Params:
//UPNPNAT_MAPPING *mapping->Port Mapping Data
//   If mapping->externalPort is 0, then
//   mapping->externalPort gets the value of mapping->internalPort
//bool tryRandom:
//   If If mapping->externalPort is in use, tries to find a free
//   random external port.
//
// Return:
//UNAT_OK:
//   Successfull.
//UNAT_EXTERNAL_PORT_IN_USE:
//   Error, you are trying to add a port mapping with an external port
//   in use.
//UNAT_NOT_IN_LAN:
//   Error, you aren't in a LAN -> no router or firewall
//UNAT_ERROR:
//   Error, use GetLastError() to get an error description.
/////////////////////////////////////////////////////////////////////////////////
MyUPnP::UPNPNAT_RETURN MyUPnP::AddNATPortMapping(UPNPNAT_MAPPING *mapping, bool tryRandom)
{
CString ProtoStr, Proto;

if(!IsLANIP(GetLocalIP())){
SetLastError(_T("You aren't behind a Hardware Firewall or Router"));
return UNAT_NOT_IN_LAN;
}

if (!isComplete()) {
Search();
if (!isComplete()) {
   SetLastError(_T("Can not found a UPnP Router"));
   return UNAT_ERROR;
}
}

if (mapping->protocol == UNAT_TCP){
Proto = _T("TCP");
ProtoStr = _T("TCP");
}
else {
Proto = _T("UDP");
ProtoStr = _T("UDP");
}

if(mapping->externalPort == 0)
mapping->externalPort = mapping->internalPort;

//WORD rndPort = mapping->externalPort;
for (int retries = 255; retries; retries--) {
CString Desc;
Desc.Format(_T("eMule (%s) [%s: %u]"), mapping->description, ProtoStr, mapping->externalPort);

if (addPortmap(mapping->externalPort, mapping->internalPort, GetLocalIPStr(), Desc, Proto)) {
   m_Mappings.AddTail(*mapping);
   return UNAT_OK;
}

if (!tryRandom) {
   SetLastError(_T("External NAT port in use"));
   return UNAT_EXTERNAL_PORT_IN_USE;
}

mapping->externalPort = (WORD)(2049 + (65535 - 2049) * rand() / (RAND_MAX + 1));
}

SetLastError(_T("External NAT port in use: Too many retries"));
return UNAT_EXTERNAL_PORT_IN_USE;
}

/////////////////////////////////////////////////////////////////////////////////
// Removes a NAT Port Mapping
// Params:
//UPNPNAT_MAPPING *mapping->Port Mapping Data
//   Should be the same struct passed to AddNATPortMapping
//bool removeFromList -> Remove the port mapping from the internal list
//   Should by allways true (dafault value if not passed).
//   If you set it to false can cause an unexpected error.
//
//
// Return:
//UNAT_OK:
//   Successfull.
//UNAT_NOT_OWNED_PORTMAPPING:
//   Error, you are trying to remove a port mapping not owned by this class
//UNAT_NOT_IN_LAN:
//   Error, you aren't in a LAN -> no router or firewall
//UNAT_ERROR:
//   Error, use GetLastError() to get an error description.
/////////////////////////////////////////////////////////////////////////////////
MyUPnP::UPNPNAT_RETURN MyUPnP::RemoveNATPortMapping(UPNPNAT_MAPPING mapping, bool removeFromList)
{
if(!IsLANIP(GetLocalIP())){
SetLastError(_T("You aren't behind a Hardware Firewall or Router"));
return UNAT_NOT_IN_LAN;
}

if (!isComplete()) {
Search();
if (!isComplete()) {
   SetLastError(_T("Can not found a UPnP Router"));
   return UNAT_ERROR;
}
}

for(POSITION pos = m_Mappings.GetHeadPosition(); pos!=NULL; m_Mappings.GetNext(pos)){
UPNPNAT_MAPPING search = m_Mappings.GetAt(pos);

if (search.externalPort == mapping.externalPort
   && search.protocol == mapping.protocol)
{
   CString Proto;

   if (mapping.protocol == UNAT_TCP)
    Proto = _T("TCP");
   else
    Proto = _T("UDP");

   if (deletePortmap(mapping.externalPort, Proto)) {
    if(removeFromList)
   m_Mappings.RemoveAt(pos);
    return UNAT_OK;
   } else {
    SetLastError(_T("Error getting StaticPortMappingCollection"));
    return UNAT_ERROR;
   }
}
}

SetLastError(_T("Port mapping not owned by this class"));
return UNAT_NOT_OWNED_PORTMAPPING;
}

void MyUPnP::clearNATPortMapping()
{
UPNPNAT_MAPPING search;
POSITION pos = m_Mappings.GetHeadPosition();
while(pos){
search = m_Mappings.GetNext(pos);
RemoveNATPortMapping(search, false);
}

m_Mappings.RemoveAll();
}

bool MyUPnP::RemoveSpecifiedPort(WORD port,UPNPNAT_PROTOCOL protocol)
{
UPNPNAT_MAPPING search;
POSITION pos = m_Mappings.GetHeadPosition();
while(pos){
POSITION cur_pos = pos;
search = m_Mappings.GetNext(pos);
if(search.externalPort==port && search.protocol==protocol)
{
   RemoveNATPortMapping(search, false);
   m_Mappings.RemoveAt(cur_pos);
   return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// Initializes m_localIP variable, for future access to GetLocalIP()
/////////////////////////////////////////////////////////////////////////////////
void MyUPnP::InitLocalIP()
{
#ifndef _DEBUG
try
#endif
{
char szHost;
if (gethostname(szHost, sizeof szHost) == 0){
   hostent* pHostEnt = gethostbyname(szHost);
   if (pHostEnt != NULL && pHostEnt->h_length == 4 && pHostEnt->h_addr_list != NULL){
    UPNPNAT_MAPPING mapping;
    struct in_addr addr;

    memcpy(&addr, pHostEnt->h_addr_list, sizeof(struct in_addr));
    m_slocalIP = inet_ntoa(addr);
    m_uLocalIP = addr.S_un.S_addr;
   }
   else{
    m_slocalIP = _T("");
    m_uLocalIP = 0;
   }
}
else{
   m_slocalIP = _T("");
   m_uLocalIP = 0;
}
}
#ifndef _DEBUG
catch(...){
m_slocalIP = _T("");
m_uLocalIP = 0;
}
#endif
}

/////////////////////////////////////////////////////////////////////////////////
// Returns the Local IP
/////////////////////////////////////////////////////////////////////////////////
DWORD MyUPnP::GetLocalIP()
{
if(m_uLocalIP == 0)
InitLocalIP();

return m_uLocalIP;
}

/////////////////////////////////////////////////////////////////////////////////
// Returns a CString with the local IP in format xxx.xxx.xxx.xxx
/////////////////////////////////////////////////////////////////////////////////
CString MyUPnP::GetLocalIPStr()
{
if(m_slocalIP.IsEmpty())
InitLocalIP();

return m_slocalIP;
}

/////////////////////////////////////////////////////////////////////////////////
// Sets the value of m_lastError (last error description)
/////////////////////////////////////////////////////////////////////////////////
void MyUPnP::SetLastError(CString error) {
m_slastError = error;
};

/////////////////////////////////////////////////////////////////////////////////
// Returns the last error description in a CString
/////////////////////////////////////////////////////////////////////////////////
CString MyUPnP::GetLastError()
{
return m_slastError;
}

/////////////////////////////////////////////////////////////////////////////////
// Returns true if nIP is a LAN ip, false otherwise
/////////////////////////////////////////////////////////////////////////////////
bool MyUPnP::IsLANIP(DWORD nIP){
// filter LAN IP's
// -------------------------------------------
// 0.*
// 10.0.0.0 - 10.255.255.255class A
// 172.16.0.0 - 172.31.255.255class B
// 192.168.0.0 - 192.168.255.255 class C

unsigned char nFirst = (unsigned char)nIP;
unsigned char nSecond = (unsigned char)(nIP >> 8);

if (nFirst==192 && nSecond==168) // check this 1st, because those LANs IPs are mostly spreaded
return true;

if (nFirst==172 && nSecond>=16 && nSecond<=31)
return true;

if (nFirst==0 || nFirst==10)
return true;

return false;
}

meigreat 发表于 2007-12-31 13:38:55

新手

谢谢版主,正在学习p2p中....感谢acejoy重新开放:)

jnwolf666 发表于 2008-4-13 16:01:00

不错,不错。顶你:lol
页: [1]
查看完整版本: P2P端口映射 UPnP功能和使用详解