1. 程式人生 > 其它 >SOCK_RAW ICMP 協議 PING

SOCK_RAW ICMP 協議 PING

技術標籤:C/C++Network

SOCK_RAW ICMP 協議 PING


以前的筆記,移過來了。

ICMP需要使用 原始套接字
原始套接字是允許訪問底層傳輸的一種套接字協議,它們可能會被惡意利用,因此 僅 Administrator使用者組有許可權建立SOCK_RAW型別的套接字。

任何人在NT下都可以建立原始套接字,但沒有Administrator許可權的人不能用它做任何事情,因為bind函式會直接返回錯誤,錯誤碼:WSAEACCESS

如果需要繞開管理員許可權,可以使用windows提供的IcmpSendEcho系列函式。
在傳送ping請求的時候,我只封裝了一個ICMP報文

,並沒有自己手動新增IP頭,封裝IP報文。
因為核心會自動新增IP頭,如果想自己新增IP頭,可以呼叫setsockopt設定IP_HDRINCL選項,告訴核心由我們自己來封裝IP頭。

ICMP 格式
Head:
8bit(1位元組) 的型別 具體訊息型別程式碼 上網查。
8bit(1位元組) 的功能程式碼
16bit(2位元組) 的校驗和
頭部共佔4位元組。
Body:
取決於型別和程式碼
選項資料:
隨意

校驗和計算:
將資料以WORD(SHORT)為單位累加到一個DWORD型別中,如果資料長度為奇數,最後一個BYTE將被擴充套件到WORD
這時累加的結果是一個WORD,最後將這個WORD的高16位和低16位相加,然後取反,就是校驗和了。

#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#pragma warning(disable:4996)//為了直接使用inet aton 或者 inet addr 這裡懶得用pton
#pragma comment(lib,"ws2_32.lib")

//定義IP首部格式
typedef struct _IPHeader {
	u_char VIHL; //版本和首部長度
	u_char ToS; //服務型別
	u_short TotalLen; //總長度
	u_short ID;
//標識號 u_short Frag_Flags; //片偏移量 u_char TTL; //生存時間 u_char Protocol; //協議 u_short Checksum; //首部校驗和 struct in_addr SrcIP; //源IP地址 struct in_addr DestIP; //目的地址 }IPHDR, *PIPHDR; /* ICMP 訊息結構 */ typedef struct _icmp_hdr { //head unsigned char icmp_type;//訊息型別 unsigned char icmp_code;//功能程式碼 unsigned short icmp_checksum;//校驗和 //body unsigned short icmp_id;//唯一標識此請求的ID號,通常設定為程序ID unsigned short icmp_sequence;//序列號 一般從0開始(沒有強制性,從任何數字開始都可以),每傳送一次新的回顯請求就加1。因為ICMP是在IP資料報內部被傳輸的,而IP協議又是不可靠、無連線的,所以ping程式打印出返回的每個分組的序列號,方便我們檢視是否有分組丟失、失序或重複。 unsigned long icmp_timestamp;//時間戳 }ICMP_HDR,*PICMP_HDR; //計算校驗和 WORD checkSum(WORD* wBuff, int nSize) { DWORD dwSum = 0;; //將資料以WORD為單位累加到wSum while (nSize > 1) { dwSum += *wBuff++; nSize -= sizeof(WORD); } //若出現最後還剩一個位元組繼續與前面結果相加(也就是為奇數的情況)。 if (nSize) { dwSum += *(BYTE*)wBuff++; } //將wSum的高16位和低16位相加,然後取反 dwSum = (dwSum >> 16/*這裡無腦高16位*/) + (dwSum & 0xffff/*這裡和(0x0000ffff)做與運算 取低16位*/); //先取和 dwSum += (dwSum >> 16);//如果還有高於16位,將繼續與低16位相加 return (WORD)(~dwSum); } int main() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (INVALID_SOCKET == s) { printf_s("建立套接字失敗%d \n", WSAGetLastError()); closesocket(s); s = INVALID_SOCKET; system("pause"); return -1; } int nNetTimeout = 1000; //1秒 //傳送時限 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char *)&nNetTimeout, sizeof(int)); //接收時限 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(int)); SOCKADDR_IN destSin; destSin.sin_family = AF_INET; destSin.sin_port = htons(0); destSin.sin_addr.S_un.S_addr = inet_addr("123.125.114.144"); //建立ICMP封包 char buff[sizeof(ICMP_HDR) + 32] = {0}; PICMP_HDR pIcmp = (PICMP_HDR)buff; pIcmp->icmp_type = 0x08;//請求回顯 pIcmp->icmp_id = (USHORT)GetCurrentProcessId(); //記得轉成USHORT //填充資料部分,可以為任意內容 也就是結構體的之後多出來的32位 memset((char*)pIcmp + sizeof(ICMP_HDR), 'S', 32); USHORT nSeq = 0; char recvBuff[1024] = {0}; SOCKADDR_IN fromSin; int nLen=sizeof(fromSin); //開始傳送/接收ICMP封包 while (true) { static int nCount = 0;//用來記錄回顯多少次 if (nCount++ == 4) break; //每次重新迴圈之前,重置結構部分資料 pIcmp->icmp_checksum = 0;//這裡必須重置為0 因為它是在buff裡的,直接去算會加上上一次計算的值 將導致第二次開始 全部出錯 pIcmp->icmp_timestamp = GetTickCount(); pIcmp->icmp_sequence = nSeq++; //計算校驗和 pIcmp->icmp_checksum = checkSum((WORD*)buff,sizeof(buff)); int nRet = sendto(s, buff, sizeof(buff), 0, (sockaddr*)&destSin, sizeof(destSin)); if (nRet == SOCKET_ERROR){ printf_s("傳送失敗 %d\n", WSAGetLastError()); system("pause"); return -1; } nRet = recvfrom(s, recvBuff, sizeof(recvBuff), 0, (sockaddr*)&fromSin, &nLen); if (nRet == SOCKET_ERROR) { DWORD dwError = WSAGetLastError(); if (dwError == WSAETIMEDOUT){ printf_s("接收超時 \n"); continue; } printf_s("接收失敗%d \n", dwError); system("pause"); return -1; } //開始解析ICMP封包 int nTick = GetTickCount(); if (nRet < sizeof(IPHDR) + sizeof(ICMP_HDR)) { printf_s("接收的位元組數太小 來自:%s %d %d\n",inet_ntoa(fromSin.sin_addr),nRet, sizeof(ICMP_HDR)); } PICMP_HDR pRecvIcmp = (PICMP_HDR)(recvBuff + sizeof(IPHDR)); if (pRecvIcmp->icmp_type != 0) /*回顯*/{ printf_s("接收沒有迴應 型別:%d \n", pRecvIcmp->icmp_type); system("pause"); return -1; } if (pRecvIcmp->icmp_id != GetCurrentProcessId()) { printf_s("獲取到其他程序的封包了 ID::%d \n", pRecvIcmp->icmp_id); system("pause"); return -1; } printf_s("%d 位元組 來自:%s", nRet, inet_ntoa(fromSin.sin_addr)); printf_s(" 迴應 = %d.", pRecvIcmp->icmp_sequence); printf_s(" 時間 = %d ms.\n", nTick - pRecvIcmp->icmp_timestamp); Sleep(1000); } closesocket(s); s = INVALID_SOCKET; WSACleanup(); }