- Windows環(huán)境下實(shí)現(xiàn)原始UDP數(shù)據(jù)包發(fā)送
-
- 作者:Hokkien
雖然Windows XP SP2已經(jīng)不再支持原始TCP數(shù)據(jù)包的發(fā)送,,但就其本身作為一項(xiàng)技術(shù)而言,,掌握原始數(shù)據(jù)包的發(fā)送也是非常重要的,。今天我們要討論的原始UDP數(shù)據(jù)包的構(gòu)造,便是這項(xiàng)技術(shù)的應(yīng)用,。相信懂得了如何管理UDP頭,,其他協(xié)議的封裝應(yīng)該就不成問題了。在閱讀本文,,你需要具備以下知識(shí):熟悉C語言,、Socket基礎(chǔ)知識(shí)和TCP/IP基礎(chǔ)知識(shí)。如果你已經(jīng)掌握了上面的知識(shí),,那就讓我們行動(dòng)吧,。 數(shù)據(jù)包格式 在對(duì)數(shù)據(jù)包進(jìn)行封裝之前,我們有必要了解一下數(shù)據(jù)報(bào)格式,,圖1是IP頭格式,。我們所學(xué)的知識(shí)絕大部分都是從資料書籍中來的,,但資料畢竟是死的,當(dāng)我們拿到圖1所表示的格式時(shí),,似乎有點(diǎn)蒙——這個(gè)格式是什么意思?。吭趺纯??我記得我初學(xué)的時(shí)候就老犯這種糊涂,。下面具體說明一下。
圖1
在認(rèn)識(shí)該格式之前,,我們有必要了解一下什么是“大尾”,,什么是“小尾”?!按笪病本褪歉呶蛔止?jié)排放在內(nèi)存的低端,,低位字節(jié)排放在內(nèi)存的高端?!靶∥病狈粗?。Intel處理器大多數(shù)使用小尾字節(jié)序,Motorola處理器大多數(shù)使用大尾(Big Endian)字節(jié)序,。既然不同的處理器處理的方式不一樣,,那么在網(wǎng)絡(luò)交流數(shù)據(jù)的時(shí)候便應(yīng)該使用同一套標(biāo)準(zhǔn),不然肯定會(huì)發(fā)生錯(cuò)誤的,。TCP/IP各層協(xié)議將字節(jié)序定義為大尾,,因此TCP/IP協(xié)議中使用的字節(jié)序通常稱之為網(wǎng)絡(luò)字節(jié)序,因而在填充數(shù)據(jù)包的時(shí)候一定要注意字節(jié)順序,,不然會(huì)出錯(cuò),!還有,圖1的數(shù)據(jù)是從左至右字節(jié)由低向高,,這一點(diǎn)注意一下,,初學(xué)者容易犯錯(cuò),。 上面是一些需要注意的地方,,下面再說一下各個(gè)字段的含義。
1)版本號(hào):標(biāo)志版本,; 2)分組長(zhǎng)度(HLEN):報(bào)文頭部的字?jǐn)?shù)(字長(zhǎng)=32bits),; 3)業(yè)務(wù)類型(Type of Service):分組的處理方式; 4)總長(zhǎng)度(Total Length):分組頭部和數(shù)據(jù)的總長(zhǎng)度(字節(jié)數(shù)),; 5)標(biāo)識(shí)(Identification),、標(biāo)記(Flags)、片偏移(Frag Offset):對(duì)分組進(jìn)行分片,,以便允許網(wǎng)上不同MTU時(shí)能進(jìn)行傳送,; 6)生存時(shí)間(TTL):規(guī)定分組在網(wǎng)上傳送的最長(zhǎng)時(shí)間(秒),,防止分組無休止地要求網(wǎng)絡(luò)搜尋不存在的目的地址; 7)協(xié)議(Protocol):發(fā)送分組的上層協(xié)議號(hào)(TCP= 6,,UDP=17),; 8)校驗(yàn)和(Header Checksum):分組頭校驗(yàn)和; 9)源和目的IP地址(Source and Destination IP Address):標(biāo)識(shí)網(wǎng)絡(luò)終端設(shè)備的IP地址,; 10)IP選項(xiàng)(IP Options):網(wǎng)絡(luò)測(cè)試,、調(diào)試、保密及其他,; 11)數(shù)據(jù)(Data):上層協(xié)議數(shù)據(jù),。 根據(jù)上面的說明我們可以定義以下IP頭結(jié)構(gòu)。
typedef struct _IPHeader // 20字節(jié)的IP頭 { UCHAR iphVerLen; // 版本號(hào)和頭長(zhǎng)度(各占4位) UCHAR ipTOS; // 服務(wù)類型 USHORTipLength; // 封包總長(zhǎng)度,,即整個(gè)IP報(bào)的長(zhǎng)度 USHORTipID; // 封包標(biāo)識(shí),,惟一標(biāo)識(shí)發(fā)送的每一個(gè)數(shù)據(jù)報(bào) USHORTipFlags; // 標(biāo)志 UCHAR ipTTL; // 生存時(shí)間,就是TTL UCHAR ipProtocol // 協(xié)議,,可能是TCP,、UDP、ICMP等 USHORTipChecksum; // 校驗(yàn)和 ULONG ipSource; // 源IP地址 ULONG ipDestination; // 目的IP地址 } IPHeader, *PIPHeader;
有了IP頭,,下面就應(yīng)該是UDP頭了,,如圖2所示。下面說一下各個(gè)字段的含義,。 圖2
1)源端口(Source Port):呼叫端端口號(hào),; 2)目的端口(Destination Port):被叫端端口號(hào); 3)報(bào)頭長(zhǎng)度(HLEN):報(bào)文頭部的字節(jié)數(shù),; 4)校驗(yàn)和(Checksum):報(bào)頭和數(shù)據(jù)字段的校驗(yàn)和,; 5)數(shù)據(jù)(Data):上層協(xié)議數(shù)據(jù)。
下面是定義的UDP頭結(jié)構(gòu),。 typedef struct _UDPHeader { USHORT sourcePort; // 源端口號(hào) USHORT destinationPort;// 目的端口號(hào) USHORT len; // 封包長(zhǎng)度 USHORT checksum; // 校驗(yàn)和 } UDPHeader, *PUDPHeader;
上面我詳細(xì)介紹了IP頭和UDP頭的格式,。在我們填充數(shù)據(jù)包的時(shí)候,應(yīng)該清楚IP頭,、UDP頭和傳輸數(shù)據(jù)的順序應(yīng)該與如圖3一致,。 圖3
編程實(shí)現(xiàn) 下面我們來看看發(fā)送原始UDP封包的代碼是如何實(shí)現(xiàn)的。首先我們要有一個(gè)IP校驗(yàn)碼,,這有前人寫好的專門代碼,,我們不必深究,拿來用就行,,再此不貼出來,,大家看雜志相關(guān)即可。下面是主程序的代碼,,很簡(jiǎn)單,,大家慢慢體會(huì),,相信會(huì)有所收獲的。
int main() {// 輸入?yún)?shù)信息 char szDestIp[] = "88.88.88.88"; // <<== 填寫目的IP地址 char szSourceIp[] = "127.0.0.1"; // <<== 填寫你自己的IP地址
USHORT nDestPort = 4567; //目的端口 USHORT nSourcePort = 8888;//源端口 char szMsg[] = "大家好,,我是Hokkien,! "; int nMsgLen = strlen(szMsg);
// 創(chuàng)建原始套節(jié)字 SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
// 有效IP頭包含選項(xiàng) BOOL bIncl = TRUE; ::setsockopt(sRaw, IPPROTO_IP, IP_HDRINCL, (char *)&bIncl, sizeof(bIncl)); char buff[1024] = { 0 }; // 填充IP頭 IPHeader *pIphdr = (IPHeader *)buff; pIphdr->iphVerLen = (4<<4 | (sizeof(IPHeader)/sizeof(ULONG))); //版本與長(zhǎng)度 pIphdr->ipLength = ::htons(sizeof(IPHeader) + sizeof(UDPHeader) + nMsgLen); //數(shù)據(jù)包長(zhǎng)度 pIphdr->ipTTL = 128; //生存時(shí)間 pIphdr->ipProtocol = IPPROTO_UDP;//UDP pIphdr->ipSource = ::inet_addr(szSourceIp); //源IP pIphdr->ipDestination = ::inet_addr(szDestIp); //目的IP pIphdr->ipChecksum = checksum((USHORT*)pIphdr, sizeof(IPHeader)); //校驗(yàn)碼,這是必需的,! // 填充UDP頭 UDPHeader *pUdphdr = (UDPHeader *)&buff[sizeof(IPHeader)]; pUdphdr->sourcePort = htons(8888); //源端口 pUdphdr->destinationPort = htons(nDestPort);//目的端口 pUdphdr->len = htons(sizeof(UDPHeader) + nMsgLen);//報(bào)頭長(zhǎng)度 pUdphdr->checksum = 0; //校驗(yàn)和,,不是必需的
char *pData = &buff[sizeof(IPHeader) + sizeof(UDPHeader)]; memcpy(pData, szMsg, nMsgLen); //填充校驗(yàn)和 ComputeUdpPseudoHeaderChecksum(pIphdr, pUdphdr, pData, nMsgLen); // 設(shè)置目的地址 SOCKADDR_IN destAddr = { 0 }; destAddr.sin_family = AF_INET; destAddr.sin_port = htons(nDestPort); destAddr.sin_addr.S_un.S_addr = ::inet_addr(szDestIp); // 發(fā)送原始UDP封包 int nRet; for(int i=0; i<5; i++) { nRet = ::sendto(sRaw, buff, sizeof(IPHeader) + sizeof(UDPHeader) + nMsgLen, 0, (sockaddr*)&destAddr, sizeof(destAddr)); if(nRet == SOCKET_ERROR) { printf(" sendto() failed: %d ", ::WSAGetLastError()); break; } else { printf(" sent %d bytes ", nRet); } }
::closesocket(sRaw);
getchar(); return 0; }
總結(jié) 需要注意的是,如果這段代碼在Windows XP SP2以前的操作系統(tǒng)上運(yùn)行,,可以使用假的源IP地址,。但遺憾的是,Windows XP SP2中,,則必須指定一個(gè)有效的IP地址,,而且不是回環(huán)IP(即127.0.0.1)。Windows現(xiàn)在已經(jīng)不支持原始TCP的發(fā)送了,。這些種種限制,,使得我們?cè)赬P上玩黑玩得很不自在!為了取消這種限制,,我們可以開發(fā)自己的驅(qū)動(dòng),,但開發(fā)驅(qū)動(dòng)畢竟不是簡(jiǎn)單之事,我等菜鳥哪來這等本事啊,。不過可喜的是,,已經(jīng)有高人為我們開發(fā)了一套當(dāng)今非常流行的網(wǎng)絡(luò)開發(fā)包驅(qū)動(dòng)Winpcap,完全可以取消上面的限制,!對(duì)于這種方法,,我們以后再討論,大家期待一下,,呵呵
|