⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 upnp.cpp

📁 一个udp穿透nat的源代码
💻 CPP
📖 第 1 页 / 共 2 页
字号:
#include "upnp.h"
#include "qsocket.h"
#include "qudp.h"
/*
ReloadIp: 获取本机上最类似于192.168.0.x的内网IP
如果本机没有内网IP,则返回INADDR_NONE
*/
static ulong ReloadIp()//get 192.168.0.x
{
	int				len = 256;
	char			name[256+1];
	hostent			*host;
	ulong			tmpip = INADDR_NONE, 
					retip = INADDR_NONE;
	memset(name, 0, len+1);
	if(gethostname(name,len)==0)
	{
		host = gethostbyname(name);
		if(host&&host->h_length>=sizeof(ulong))
		{
			len = 0;
			while(host->h_addr_list[len])
			{
				tmpip = *((ulong*)(host->h_addr_list[len++]));
				if(((tmpip&0xFF)==192&&((tmpip>>8)&0xFF)==168&&((tmpip>>16)&0xFF)==0)
					||IsLAN(tmpip)||!is_validip(retip))
				{
					retip = tmpip;
				}
			}
		}
	}
	return retip;
}

static const ulong	UPNPADDR = 0xFAFFFFEF;
static const int	UPNPPORT = 1900;
static const char*	URNPREFIX = "urn:schemas-upnp-org:";
static const char*  LCA = "Location:";
static const char*  LCB = "LOCATION:";
static const char*  LCC = "location:";
/*
LCA,LCB,LCC: 有些UPnP设备以LCA作为字段名称,有些则用LCB,有些则用LCC,对于其他那些喜欢乱命名的厂商只好说抱歉了.
*/
QMyUPNP::QMyUPNP()
: m_version(1)
{
}
QMyUPNP::QMyUPNP(const QString& device)
: m_devicename(device), m_version(1)
{

}
QMyUPNP::~QMyUPNP()
{
	Clear();
}
/*InternalClear: 清空对象,但保留对象*/
void QMyUPNP::InternalClear()
{
	m_baseurl = "";
	m_controlurl = "";
	m_friendlyname = "";
	m_modelname = "";
}
/*Clear: 清空对象,同时对象失效.*/
void QMyUPNP::Clear()
{
	m_name = "";
	m_description = "";
	m_version = 1;
	InternalClear();
}
/*
InternalSearch: 查找UPnP设备
实现过程:首先调用InternalSearch2进行查找的操作,如果没有找到,则设置对象的设备名为IGD,再次调用InternalSearch2查找.
之所以分两次查找是因为有些设备支持以服务名查找,而有些设备则只支持以设备名查找.
参数含义:
name: 要查找的服务/设备的名称
type: name表示的是服务或设备(service/device)
repeat: 尝试查找的次数
delay: 查找结果的最长等待时间
version: UPnP版本,默认1
ip: 查询的目标IP,默认为UPnP的广播IP0xFAFFFFEF,也可以指定IP以便当程序运行在某些主机上时也能查找.
lpCall: 一个回调函数,在查找过程中随机调用.
返回值: 成功返回true,否则返回false
*/
bool QMyUPNP::InternalSearch(const QString& name, const char* type, int repeat,
							 int delay, int version, ulong ip, PUPNPCALLBACK lpCall)
{
	if(InternalSearch2(name,type,repeat,delay,version,ip,lpCall))return true;
	QString back = m_devicename;
	m_devicename = QString("urn:schemas-upnp-org:device:InternetGatewayDevice:") + m_version;
	bool ok = InternalSearch2(name,type,repeat,delay,version,ip,lpCall);
	m_devicename = back;
	return ok;
}
/*
InternalSearch2: 执行查找操作.
实现过程:
创建UDP套接字,如果IP为0,则先对255.255.255.255的1900端口发送广播消息,再对UPnP的广播IP发送消息.
参数函数同InternalSearch.
*/
bool QMyUPNP::InternalSearch2(const QString& name, const char* type, int repeat,
							 int delay, int version, ulong ip, PUPNPCALLBACK lpCall)
{
	static const char*	USRS = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n"
							  "MAN: \"ssdp:discover\"\r\nMX: ";
	const int			RCVLEN = 4096;
	Clear();
	if(name.IsEmpty())return false;
	if(type==NULL)type = "service";
	if(repeat<=0)repeat = UPNPREPEAT;
	if(delay<=0)delay = UPNPDELAY;
	if(version<=0)version = 1;
	m_version = version;
	m_name = QString(URNPREFIX) + type + ":" + name + ":" + version;
	QString finding = m_devicename.IsEmpty() ? m_name : m_devicename;
	QString shrs = QString(USRS) + delay + "\r\nST: " + finding + "\r\n\r\n";
	QUdpSocket	udp(true);
	char 		a[50], b[50], c[50];
	char		rcv[RCVLEN + 1];
	int			rlen = 0;
	int			retry = 0;
	const char* lc = "";
	const char* le = "";
	bool		broad = (!is_validip(ip)); //send to 255.255.255.255
	do
	{
		if(retry)sleep(1);
		if(!is_validip(ip)) ip = UPNPADDR;
		if(broad&&!udp.SendPacket(shrs.c_str(),shrs.Length(),INADDR_BROADCAST,UPNPPORT,delay/2))continue;
		if(!udp.SendPacket(shrs.c_str(),shrs.Length(),ip,UPNPPORT,delay/2)
		   ||(rlen=udp.RecvPacket(rcv,RCVLEN,delay))<=0)continue;
		if(lpCall&&!lpCall(NULL))break;
		if(rlen<=0||rlen>RCVLEN)continue;
		rcv[rlen] = 0;
		if(SplitterString(rcv,a,b,c,40,40,40)!=3||b[0]!='2')continue;
		if(strstr(rcv,finding.c_str())==NULL)continue;
		lc = strstr(rcv,LCA);
		if(lc==NULL) lc = strstr(rcv,LCB);
		if(lc==NULL) lc = strstr(rcv,LCC);
		if(lc==NULL)continue;
		lc += strlen(LCA);
		while(lc[0]>0&&lc[0]<=0x20) lc++;
		le = strchr(lc,'\n');
		if(le==NULL)continue;
		m_description = QString(lc,le - lc).Trim();
	}while(++retry<repeat&&!Valid());
	return GetDescription(delay);
}
/*
Search: QMyUPnP的公开接口,由外部对象调用进行设备或服务的查找.
执行过程: 先对默认IP调用InternalSearch进行查找,如果没找到,再判断本机是否有局域网IP,如果有,则指定最类似于192.168.0.x的IP进行查询.
参数函数同InternalSearch.
*/
bool QMyUPNP::Search(const QString& name, const char* type, int repeat, int delay,
					 int version, PUPNPCALLBACK lpCall)
{
	ulong	rip = 0;
	if(InternalSearch(name,type,repeat,delay,version,0,lpCall))return true;
	rip = ReloadIp();
	if(is_validip(rip))return InternalSearch(name,type,repeat,delay,version,rip,lpCall);
	return false;
}
/*
SetDescriptionUrl: 由外部对象调用不通过查询过程来创建一个有效的UPnP对象.
实现过程:对指定的URL执行GetDescription操作来判断是否有效的UPnP对象.
参数含义:
name: 服务/设备名称(全称:如:urn:schemas-upnp-org:service:WANPPPConnection:1)
descri: 用于获取设备/服务描述的目标URL.
*/
bool QMyUPNP::SetDescriptionUrl(const QString& name, const QString& descri)
{
	Clear();
	m_name = name;
	m_description = descri;
	return GetDescription();
}
inline bool PPIsDigitChar(const char p)
{
	return (p>='0'&&p<='9');
}
static int ParseInteger(const char* cstr)
{
	const unsigned char* str = (const unsigned char*) cstr;
	int value = 0;
	assert(str);
	if(str)
	{
		int retry = 20;
		bool neg = (str[0]=='-');
		if(neg)str++;
		while(PPIsDigitChar(str[0])&&(--retry)>0)
		{
			value*=10;
			value+=(str[0]-'0');
			str++;
		}
		if(neg)value = -value;
	}
	return value;
}

static QString NGetAddressFromUrl(const QString& str, QString& post, QString& host, int& port)
{
	port = 0;
	post = "";
	if(str.IsEmpty())return QString();
	const char* src = str.c_str();
	const char* start,
			  * pstart,
			  *	end;
	start = strstr(src,"://");
	start = (start==NULL)?src:(start+3);
	end = strchr(start,'/');
	if(end==NULL)end = src + strlen(src);
	host = QString(start,end-start);
	post = (end[0]=='/')?QString(end):QString("/");
	pstart = strchr(start,':');
	port = (pstart==NULL||pstart>=end)?80:ParseInteger(pstart+1);
	if(port==0)port = 80;
	if(pstart&&pstart<end)end = pstart;
	return QString(start,end-start);
}
/*
GetDescription: 在目标对象的描述URL已经获取的情况下获取该对象的描述.
实现过程: 对指定的URL实行一个UPnP规范指定的GET操作,解析返回的模板.只有成功解析了,对象才有效.
参数: 
delay: 最大等待时间.
*/
bool QMyUPNP::GetDescription(int delay)
{
	if(!Valid())return false;
	InternalClear();
	QString post, host, addr;
	int port = 0;
	addr = NGetAddressFromUrl(m_description, post, host, port);
	if(addr.IsEmpty()||!is_validport(port))return false;
	QString get = QString("GET ") + post + " HTTP/1.1\r\nHOST: " + host + "\r\nACCEPT-LANGUAGE: en\r\n\r\n";
	QProxy	proxy; //因为UPnP设备基本上是局域网内部的操作,不需要代理,为了避免全局的代理设置影响后续TCP操作,这里定义一个空的代理.
	QSocket tcp;
	int		retry = 0;
	if(delay<=0)delay = UPNPDELAY;
	if(!tcp.Connect(addr.c_str(),port,false,&proxy))return false;//connection failed
	tcp.SendData(get.c_str(),get.Length());
	do
	{
		if(tcp.Write()>=0&&tcp.SendLength()>0) sleep(1); //sleep(x) = Sleep(x * 1000)
	}while(++retry<delay&&tcp.SendLength()>0);
	if(tcp.SendLength()>0||tcp.Read()<0)return false;
	retry = 0;
	do
	{
		if(retry)sleep(1);
	}while(tcp.Read()>=0&&++retry<delay&&tcp.Read()>=0);
	char a[50], b[50], c[50];
	if(SplitterString(tcp.RecvBuffer(),a,b,c,40,40,40)!=3||b[0]!='2')return false;
	const char* lfe = NULL;
	const char* buf = tcp.RecvBuffer(); //如果当前没有数据,返回"",RecvBuffer永远不会返回NULL,所以不用担心空指针
	const char* lfr = NULL;
	//get friendly name
	lfr = strstr(buf,"<friendlyName>");
	if(lfr)
	{
		lfr += (sizeof("<friendlyName>") - 1);
		lfe = strstr(lfr,"</friendlyName>");
		if(lfe)m_friendlyname = QString(lfr, lfe - lfr).Trim();
	}
	//get <modelName>
	lfr = strstr(buf,"<modelName>");

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -