C++如何實現DNS域名解析
這片文章介紹了C++如何實現DNS域名解析,還有對相關技術的介紹,代碼很詳細,需要的朋友可以參考下
一、概述
現在來搞定DNS域名解析,其實這是前面一篇文章C++實現Ping里面的遺留問題,要干的活是ping的過程中畫紅線的部分:
cmd下域名解析的命令是nslookup,比如“nslookup?www.baidu.com”的結果如下:
其中,Address返回的就是www.baidu.com對應的IP地址,這個可能有多個
Alias指別名,也就是說www.baidu.com是www.a.shifen.com的別名,而www.a.shifen.com則是www.baidu.com的規范名(Canonical Name,CName),具體參考RFC1035 3.2.2 & wikipedia
?
二、實現結果預覽
先看一下最終搞成了什么樣子
輸入:域名字符串
輸出:IP列表、CName列表、DNS查詢所用時間
三、相關技術
?
3.1、UDP or TCP ? (RFC1035 4.2)
UDP:DNS查詢和回復采用低開銷高性能的UDP,端口號為53。
TCP:輔助DNS服務器從主DNS服務器拉取最新數據時,采用可靠的TCP傳輸,端口號也為53。
我們這里做DNS查詢采用UDP,53端口。
3.2、DNS查詢/回復包頭部解析 (RFC1035 4.1.1)
重點介紹一下我們關心的部分:
ID(16bits):標識符,一般填入本進程的標識符
QR(1bits):標志位,查詢包為0,回復包為1
Opcode(4bits):查詢的種類,標準查詢為0
QDCOUNT(16bits):DNS查詢/回復包數據部分Question字段的個數
ANCOUNT(16bits):DNS查詢/回復包數據部分Answer字段的個數
3.2、DNS查詢/回復包數據部分解析 (RFC1035 4.1.2 & 4.1.3)
查詢/回復包的數據部分依次為QDCOUNT個Question字段、ANCOUNT個Answer字段....
對于任意字段,其格式如下:
Name(不定長):域名,這部分的格式比較復雜,后面單獨說。
TYPE(16bits):查詢類型/回復包RDATA類型,比如TYPE=1表示主機IP地址、TYPE=5表示CNAME,詳見RFC1035 3.2.2
CLASS(16bits):類,一般情況下CLASS=1表示Internet,詳見RFC1035 3.2.4
TTL(32bits,僅回復包):生存時間
RDLENGTH(16bits,僅回復包):RDATA部分的字節數
RDATA(不定長,僅回復包):資源數據,具體格式取決于TYPE和CLASS,比如TYPE=1、CLASS=1時,RDATA為四個字節的IP地址
3.3、Name解析&消息壓縮
3.3.1、一般格式 (RFC1035 4.1.2)
標簽內容長度(1個字節) + 標簽內容,以標簽內容長度0作為Name的結束符,例如:
3.3.2、消息壓縮格式 (RFC1035 4.1.4)
如果標簽內容長度的二進制前兩位是11,則表示消息壓縮。
此時,標簽內容長度1個字節+后面的1個字節一共16位,后14位表示相對DNS包起始地址的偏移(Byte),例如:
上述例子中,DNS包起始地址為0x0000,c0 13的二進制為11000000 00010003,即跳轉偏移為0x13個字節,對應的數據為03 63 6f 6d 00。
RFC1035中規定,支持的消息壓縮規則為:
①以內容長度0結尾的標簽序列
②偏移指針
③標簽序列+偏移指針
也就是說,Name的消息壓縮要求偏移指針必須在Name的尾部,且不支持同一級存在多個偏移指針(偏移指針序列),
但Name的消息壓縮支持嵌套的偏移指針,即指針指向的偏移位置仍然是以偏移指針結尾的數據
四、代碼實現
#pragma once
//這里需要導入庫 Ws2_32.lib,在不同的IDE下可能不太一樣
//#pragma comment(lib, "Ws2_32.lib")
#include <windows.h>
#include <
string>
#include <vector>
#define MAX_DOMAINNAME_LEN 255
#define DNS_PORT 53
#define DNS_TYPE_SIZE 2
#define DNS_CLASS_SIZE 2
#define DNS_TTL_SIZE 4
#define DNS_DATALEN_SIZE 2
#define DNS_TYPE_A 0x0001
//1 a host address
#define DNS_TYPE_CNAME 0x0005
//5 the canonical name for an alias
#define DNS_PACKET_MAX_SIZE (sizeof(DNSHeader) + MAX_DOMAINNAME_LEN + DNS_TYPE_SIZE + DNS_CLASS_SIZE)
struct DNSHeader
{USHORT usTransID; //標識符USHORT usFlags;
//各種標志位USHORT usQuestionCount;
//Question字段個數 USHORT usAnswerCount;
//Answer字段個數USHORT usAuthorityCount;
//Authority字段個數USHORT usAdditionalCount;
//Additional字段個數
};class CDNSLookup
{
public:CDNSLookup();~
CDNSLookup();BOOL DNSLookup(ULONG ulDNSServerIP, char *szDomainName, std::vector<ULONG> *pveculIPList = NULL, std::vector<std::
string> *pvecstrCNameList = NULL, ULONG ulTimeout =
1000, ULONG *pulTimeSpent =
NULL);BOOL DNSLookup(ULONG ulDNSServerIP, char *szDomainName, std::vector<std::
string> *pvecstrIPList = NULL, std::vector<std::
string> *pvecstrCNameList = NULL, ULONG ulTimeout =
1000, ULONG *pulTimeSpent =
NULL);private:BOOL Init();BOOL UnInit();BOOL DNSLookupCore(ULONG ulDNSServerIP, char *szDomainName, std::vector<ULONG> *pveculIPList, std::vector<std::
string> *pvecstrCNameList, ULONG ulTimeout, ULONG *
pulTimeSpent);BOOL SendDNSRequest(sockaddr_in sockAddrDNSServer, char *
szDomainName);BOOL RecvDNSResponse(sockaddr_in sockAddrDNSServer, ULONG ulTimeout, std::vector<ULONG> *pveculIPList, std::vector<std::
string> *pvecstrCNameList, ULONG *
pulTimeSpent);BOOL EncodeDotStr(char *szDotStr,
char *
szEncodedStr, USHORT nEncodedStrSize);BOOL DecodeDotStr(char *szEncodedStr, USHORT *pusEncodedStrLen,
char *szDotStr, USHORT nDotStrSize,
char *szPacketStartPos =
NULL);ULONG GetTickCountCalibrate();private:BOOL m_bIsInitOK;SOCKET m_sock;WSAEVENT m_event;USHORT m_usCurrentProcID;char *
m_szDNSPacket;
};[DNSLookup.h]#include "DNSLookup.h"
#include <stdio.h>
#include <
string.h>
CDNSLookup::CDNSLookup() : m_bIsInitOK(FALSE), m_sock(INVALID_SOCKET),m_szDNSPacket(NULL)
{m_bIsInitOK =
Init();
}CDNSLookup::~
CDNSLookup()
{UnInit();
}BOOL CDNSLookup::DNSLookup(ULONG ulDNSServerIP, char *szDomainName, std::vector<ULONG> *pveculIPList, std::vector<std::
string> *pvecstrCNameList, ULONG ulTimeout, ULONG *
pulTimeSpent)
{return DNSLookupCore(ulDNSServerIP, szDomainName, pveculIPList, pvecstrCNameList, ulTimeout, pulTimeSpent);
}BOOL CDNSLookup::DNSLookup(ULONG ulDNSServerIP, char *szDomainName, std::vector<std::
string> *pvecstrIPList, std::vector<std::
string> *pvecstrCNameList, ULONG ulTimeout, ULONG *
pulTimeSpent)
{std::vector<ULONG> *pveculIPList =
NULL;if (!pvecstrIPList
){return FALSE;}
? ?std::vector<ULONG> veculIPList;
? ?pveculIPList = &veculIPList;
BOOL bRet =
DNSLookupCore(ulDNSServerIP, szDomainName, pveculIPList, pvecstrCNameList, ulTimeout, pulTimeSpent);if (bRet){pvecstrIPList->
clear();char szIP[
16] = {
'\0'};for (std::vector<ULONG>::iterator iter = pveculIPList->begin(); iter != pveculIPList->end(); ++
iter){BYTE *pbyIPSegment = (BYTE*)(&(*
iter));//sprintf_s(szIP, 16, "%d.%d.%d.%d", pbyIPSegment[0], pbyIPSegment[1], pbyIPSegment[2], pbyIPSegment[3]);sprintf(szIP,
"%d.%d.%d.%d", pbyIPSegment[
0], pbyIPSegment[
1], pbyIPSegment[
2], pbyIPSegment[
3]);pvecstrIPList->
push_back(szIP);}}return bRet;
}BOOL CDNSLookup::Init()
{WSADATA wsaData;if (WSAStartup(MAKEWORD(
2,
2), &wsaData) ==
SOCKET_ERROR){return FALSE;}if ((m_sock = socket(AF_INET, SOCK_DGRAM,
0)) ==
INVALID_SOCKET){return FALSE;}m_event =
WSACreateEvent();WSAEventSelect(m_sock, m_event, FD_READ);m_szDNSPacket =
new (std::nothrow)
char[DNS_PACKET_MAX_SIZE];if (m_szDNSPacket ==
NULL){return FALSE;}m_usCurrentProcID =
(USHORT)GetCurrentProcessId();return TRUE;
}BOOL CDNSLookup::UnInit()
{WSACleanup();if (m_szDNSPacket !=
NULL){delete [] m_szDNSPacket;}return TRUE;
}BOOL CDNSLookup::DNSLookupCore(ULONG ulDNSServerIP, char *szDomainName, std::vector<ULONG> *pveculIPList, std::vector<std::
string> *pvecstrCNameList, ULONG ulTimeout, ULONG *
pulTimeSpent)
{if (m_bIsInitOK == FALSE || szDomainName ==
NULL){return FALSE;}//配置SOCKET
sockaddr_in sockAddrDNSServer; sockAddrDNSServer.sin_family =
AF_INET; sockAddrDNSServer.sin_addr.s_addr =
ulDNSServerIP;sockAddrDNSServer.sin_port =
htons( DNS_PORT );//DNS查詢與解析if (!
SendDNSRequest(sockAddrDNSServer, szDomainName)|| !
RecvDNSResponse(sockAddrDNSServer, ulTimeout, pveculIPList, pvecstrCNameList, pulTimeSpent)){return FALSE;}return TRUE;
}BOOL CDNSLookup::SendDNSRequest(sockaddr_in sockAddrDNSServer, char *
szDomainName)
{char *pWriteDNSPacket =
m_szDNSPacket;memset(pWriteDNSPacket, 0, DNS_PACKET_MAX_SIZE); //填充DNS查詢報文頭部DNSHeader *pDNSHeader = (DNSHeader*
)pWriteDNSPacket;pDNSHeader->usTransID =
m_usCurrentProcID;pDNSHeader->usFlags = htons(
0x0100);pDNSHeader->usQuestionCount = htons(
0x0001);pDNSHeader->usAnswerCount =
0x0000;pDNSHeader->usAuthorityCount =
0x0000;pDNSHeader->usAdditionalCount =
0x0000;//設置DNS查詢報文內容USHORT usQType = htons(
0x0001);USHORT usQClass = htons(
0x0001);USHORT nDomainNameLen =
strlen(szDomainName);char *szEncodedDomainName = (
char *)
malloc(nDomainNameLen +
2);if (szEncodedDomainName ==
NULL){return FALSE;}if (!EncodeDotStr(szDomainName, szEncodedDomainName, nDomainNameLen +
2)){return FALSE;}//填充DNS查詢報文內容USHORT nEncodedDomainNameLen = strlen(szEncodedDomainName) +
1;memcpy(pWriteDNSPacket +=
sizeof(DNSHeader), szEncodedDomainName, nEncodedDomainNameLen);memcpy(pWriteDNSPacket += nEncodedDomainNameLen, (
char*)(&
usQType), DNS_TYPE_SIZE);memcpy(pWriteDNSPacket += DNS_TYPE_SIZE, (
char*)(&
usQClass), DNS_CLASS_SIZE);free (szEncodedDomainName);//發送DNS查詢報文USHORT nDNSPacketSize =
sizeof(DNSHeader) + nEncodedDomainNameLen + DNS_TYPE_SIZE +
DNS_CLASS_SIZE;if (sendto(m_sock, m_szDNSPacket, nDNSPacketSize,
0, (sockaddr*)&sockAddrDNSServer,
sizeof(sockAddrDNSServer)) ==
SOCKET_ERROR){return FALSE;}return TRUE;
}BOOL CDNSLookup::RecvDNSResponse(sockaddr_in sockAddrDNSServer, ULONG ulTimeout, std::vector<ULONG> *pveculIPList, std::vector<std::
string> *pvecstrCNameList, ULONG *
pulTimeSpent)
{ULONG ulSendTimestamp =
GetTickCountCalibrate();if (pveculIPList !=
NULL){pveculIPList->
clear();}if (pvecstrCNameList !=
NULL){pvecstrCNameList->
clear();}char recvbuf[
1024] = {
'\0'};char szDotName[
128] = {
'\0'};USHORT nEncodedNameLen =
0;while (TRUE){if (WSAWaitForMultipleEvents(
1, &m_event, FALSE,
100, FALSE) !=
WSA_WAIT_TIMEOUT){WSANETWORKEVENTS netEvent;WSAEnumNetworkEvents(m_sock, m_event, &
netEvent);if (netEvent.lNetworkEvents &
FD_READ){ULONG ulRecvTimestamp =
GetTickCountCalibrate();int nSockaddrDestSize =
sizeof(sockAddrDNSServer);//接收響應報文if (recvfrom(m_sock, recvbuf,
1024,
0, (
struct sockaddr*)&sockAddrDNSServer, &nSockaddrDestSize) !=
SOCKET_ERROR){DNSHeader *pDNSHeader = (DNSHeader*
)recvbuf;USHORT usQuestionCount =
0;USHORT usAnswerCount =
0;if (pDNSHeader->usTransID ==
m_usCurrentProcID&& (ntohs(pDNSHeader->usFlags) &
0xfb7f) ==
0x8100 //RFC1035 4.1.1(Header section format)&& (usQuestionCount = ntohs(pDNSHeader->usQuestionCount)) >=
0&& (usAnswerCount = ntohs(pDNSHeader->usAnswerCount)) >
0){char *pDNSData = recvbuf +
sizeof(DNSHeader);//解析Question字段for (
int q =
0; q != usQuestionCount; ++
q){if (!DecodeDotStr(pDNSData, &nEncodedNameLen, szDotName,
sizeof(szDotName))){return FALSE;}pDNSData += (nEncodedNameLen + DNS_TYPE_SIZE +
DNS_CLASS_SIZE);}//解析Answer字段for (
int a =
0; a != usAnswerCount; ++
a){if (!DecodeDotStr(pDNSData, &nEncodedNameLen, szDotName,
sizeof(szDotName), recvbuf)){return FALSE;}pDNSData +=
nEncodedNameLen;USHORT usAnswerType = ntohs(*(USHORT*
)(pDNSData));USHORT usAnswerClass = ntohs(*(USHORT*)(pDNSData +
DNS_TYPE_SIZE));ULONG usAnswerTTL = ntohl(*(ULONG*)(pDNSData + DNS_TYPE_SIZE +
DNS_CLASS_SIZE));USHORT usAnswerDataLen = ntohs(*(USHORT*)(pDNSData + DNS_TYPE_SIZE + DNS_CLASS_SIZE +
DNS_TTL_SIZE));pDNSData += (DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE +
DNS_DATALEN_SIZE);if (usAnswerType == DNS_TYPE_A && pveculIPList !=
NULL){ULONG ulIP = *(ULONG*
)(pDNSData);pveculIPList->
push_back(ulIP);}else if (usAnswerType == DNS_TYPE_CNAME && pvecstrCNameList !=
NULL){if (!DecodeDotStr(pDNSData, &nEncodedNameLen, szDotName,
sizeof(szDotName), recvbuf)){return FALSE;}pvecstrCNameList->
push_back(szDotName);}pDNSData +=
(usAnswerDataLen);}//計算DNS查詢所用時間if (pulTimeSpent !=
NULL){*pulTimeSpent = ulRecvTimestamp -
ulSendTimestamp;}break;}}}}//超時退出if (GetTickCountCalibrate() - ulSendTimestamp >
ulTimeout){*pulTimeSpent = ulTimeout +
1;return FALSE;}}return TRUE;
}/** convert "www.baidu.com" to "\x03www\x05baidu\x03com"* 0x0000 03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00 ff*/
BOOL CDNSLookup::EncodeDotStr(char *szDotStr,
char *
szEncodedStr, USHORT nEncodedStrSize)
{USHORT nDotStrLen =
strlen(szDotStr);if (szDotStr == NULL || szEncodedStr == NULL || nEncodedStrSize < nDotStrLen +
2){return FALSE;}char *szDotStrCopy =
new char[nDotStrLen +
1];//strcpy_s(szDotStrCopy, nDotStrLen + 1, szDotStr);
strcpy(szDotStrCopy, szDotStr);char *pNextToken =
NULL;//char *pLabel = strtok_s(szDotStrCopy, ".", &pNextToken);char *pLabel = strtok(szDotStrCopy,
".");USHORT nLabelLen =
0;USHORT nEncodedStrLen =
0;while (pLabel !=
NULL){if ((nLabelLen = strlen(pLabel)) !=
0){//sprintf_s(szEncodedStr + nEncodedStrLen, nEncodedStrSize - nEncodedStrLen, "%c%s", nLabelLen, pLabel);sprintf(szEncodedStr + nEncodedStrLen,
"%c%s", nLabelLen, pLabel);nEncodedStrLen += (nLabelLen +
1);}//pLabel = strtok_s(NULL, ".", &pNextToken);pLabel = strtok(NULL,
".");}delete [] szDotStrCopy;return TRUE;
}/** convert "\x03www\x05baidu\x03com\x00" to "www.baidu.com"* 0x0000 03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00 ff* convert "\x03www\x05baidu\xc0\x13" to "www.baidu.com"* 0x0000 03 77 77 77 05 62 61 69 64 75 c0 13 ff ff ff ff* 0x0010 ff ff ff 03 63 6f 6d 00 ff ff ff ff ff ff ff ff*/
BOOL CDNSLookup::DecodeDotStr(char *szEncodedStr, USHORT *pusEncodedStrLen,
char *szDotStr, USHORT nDotStrSize,
char *
szPacketStartPos)
{if (szEncodedStr == NULL || pusEncodedStrLen == NULL || szDotStr ==
NULL){return FALSE;}char *pDecodePos =
szEncodedStr;USHORT usPlainStrLen =
0;BYTE nLabelDataLen =
0; *pusEncodedStrLen =
0;while ((nLabelDataLen = *pDecodePos) !=
0x00){if ((nLabelDataLen &
0xc0) ==
0)
//普通格式,LabelDataLen + Label
{if (usPlainStrLen + nLabelDataLen +
1 >
nDotStrSize){return FALSE;}memcpy(szDotStr + usPlainStrLen, pDecodePos +
1, nLabelDataLen);memcpy(szDotStr + usPlainStrLen + nLabelDataLen,
".",
1);pDecodePos += (nLabelDataLen +
1);usPlainStrLen += (nLabelDataLen +
1);*pusEncodedStrLen += (nLabelDataLen +
1);}else //消息壓縮格式,11000000 00000000,兩個字節,前2位為跳轉標志,后14位為跳轉的偏移
{if (szPacketStartPos ==
NULL){return FALSE;}USHORT usJumpPos = ntohs(*(USHORT*)(pDecodePos)) &
0x3fff;USHORT nEncodeStrLen =
0;if (!DecodeDotStr(szPacketStartPos + usJumpPos, &nEncodeStrLen, szDotStr + usPlainStrLen, nDotStrSize -
usPlainStrLen, szPacketStartPos)){return FALSE;}else{*pusEncodedStrLen +=
2;return TRUE;}}}szDotStr[usPlainStrLen -
1] =
'\0';*pusEncodedStrLen +=
1;return TRUE;
}ULONG CDNSLookup::GetTickCountCalibrate()
{static ULONG s_ulFirstCallTick =
0;static LONGLONG s_ullFirstCallTickMS =
0;SYSTEMTIME systemtime;FILETIME filetime;GetLocalTime(&
systemtime); SystemTimeToFileTime(&systemtime, &
filetime);LARGE_INTEGER liCurrentTime;liCurrentTime.HighPart =
filetime.dwHighDateTime;liCurrentTime.LowPart =
filetime.dwLowDateTime;LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart /
10000;if (s_ulFirstCallTick ==
0){s_ulFirstCallTick =
GetTickCount();}if (s_ullFirstCallTickMS ==
0){s_ullFirstCallTickMS =
llCurrentTimeMS;}return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS -
s_ullFirstCallTickMS);
}[DNSLookup.cpp]#include <stdio.h>
#include <windows.h>
#include "DNSLookup.h"int main(
void)
{char szDomainName[] =
"www.baidu.com";std::vector<ULONG>
veculIPList;std::vector<std::
string>
vecstrIPList;std::vector<std::
string>
vecCNameList;ULONG ulTimeSpent =
0;CDNSLookup dnslookup;BOOL bRet = dnslookup.DNSLookup(inet_addr(
"114.114.114.114"), szDomainName, &vecstrIPList, &vecCNameList,
1000, &
ulTimeSpent);printf("DNSLookup result (%s):\n", szDomainName);if (!
bRet){printf("timeout!\n");return -
1;}for (
int i =
0; i != veculIPList.size(); ++
i){printf("IP%d(ULONG) = %u\n", i +
1, veculIPList[i]);}for (
int i =
0; i != vecstrIPList.size(); ++
i){printf("IP%d(string) = %s\n", i +
1, vecstrIPList[i].c_str());}for (
int i =
0; i != vecCNameList.size(); ++
i){printf("CName%d = %s\n", i +
1, vecCNameList[i].c_str());}printf("time spent = %ums\n", ulTimeSpent);return 0;
} http://www.jb51.net/article/70359.htm
總結
以上是生活随笔為你收集整理的C++如何实现DNS域名解析转的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。