ping 實現設計---ICMP

發送ICMP報文時,必須程序本身計算校驗和,將它填入ICMP頭部對應的域中。windows

校驗和的計算方法:socket

  將數據以字爲單位累加到一個雙字中,若是數據長度爲奇數,最後一個字節將被擴展到字,累加的結果是一個雙字,最後將這個雙字的高16位,低16位相加後取反,便獲得了校驗和。tcp

下面是checksum的計算校驗和的代碼:函數

USHORT checksum(USHORT* buff, int size) { unsigned long cksum = 0; while(size>1) { cksum += *buff++; size -= sizeof(USHORT); } // 是奇數
    if(size) { cksum += *(UCHAR*)buff; } // 將32位的chsum高16位和低16位相加,而後取反
    cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); }

Ping程序實例:spa

Ping用來檢查主機是否存在,是否可達。code

下面是Ping的執行步驟:blog

1 建立協議類型爲IPPROTO_ICMP的原始套接字進程

2 建立並初始化ICMP封包ip

3 調用sendto函數向遠程主機發送ICMP請求it

4 調用recvfrom函數接收ICMP響應

完整代碼以下:

///////////////////////////////////////////
// ping.cpp文件
 #include "../common/initsock.h" #include "../common/protoinfo.h" #include <stdio.h> #include <winsock2.h> #include <windows.h> #include "Ws2tcpip.h" CInitSock theSock; typedef struct icmp_hdr { unsigned char   icmp_type;        // 消息類型
    unsigned char   icmp_code;        // 代碼
    unsigned short  icmp_checksum;    // 校驗和 // 下面是回顯頭
    unsigned short  icmp_id;        // 用來唯一標識此請求的ID號,一般設置爲進程ID
    unsigned short  icmp_sequence;    // 序列號
    unsigned long   icmp_timestamp; // 時間戳
} ICMP_HDR, *PICMP_HDR; USHORT checksum(USHORT* buff, int size); BOOL SetTTL(SOCKET s, int nValue); BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv = TRUE); USHORT checksum(USHORT* buff, int size) { unsigned long cksum = 0; while(size>1) { cksum += *buff++; size -= sizeof(USHORT); } // 是奇數
    if(size) { cksum += *(UCHAR*)buff; } // 將32位的chsum高16位和低16位相加,而後取反
    cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); } BOOL SetTTL(SOCKET s, int nValue) { int ret = ::setsockopt(s, IPPROTO_IP, IP_TTL, (char*)&nValue, sizeof(nValue)); return ret != SOCKET_ERROR; } BOOL SetTimeout(SOCKET s, int nTime, BOOL bRecv) { int ret = ::setsockopt(s, SOL_SOCKET, bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime)); return ret != SOCKET_ERROR; } int main() { // 目的IP地址,即要Ping的IP地址
    char szDestIp[] = "127.0.0.1";    // 127.0.0.1 // 建立原始套節字
    SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 設置接收超時
    SetTimeout(sRaw, 1000, TRUE); // 設置目的地址
 SOCKADDR_IN dest; dest.sin_family = AF_INET; dest.sin_port = htons(0); dest.sin_addr.S_un.S_addr = inet_addr(szDestIp); // 建立ICMP封包
    char buff[sizeof(ICMP_HDR) + 32]; ICMP_HDR* pIcmp = (ICMP_HDR*)buff; // 填寫ICMP封包數據
    pIcmp->icmp_type = 8;    // 請求一個ICMP回顯
    pIcmp->icmp_code = 0; pIcmp->icmp_id = (USHORT)::GetCurrentProcessId(); pIcmp->icmp_checksum = 0; pIcmp->icmp_sequence = 0; // 填充數據部分,能夠爲任意
    memset(&buff[sizeof(ICMP_HDR)], 'E', 32); // 開始發送和接收ICMP封包
    USHORT    nSeq = 0; char recvBuf[1024]; SOCKADDR_IN from; int nLen = sizeof(from); while(TRUE) { static int nCount = 0; int nRet; if(nCount++ == 4) break; pIcmp->icmp_checksum = 0; pIcmp->icmp_timestamp = ::GetTickCount(); pIcmp->icmp_sequence = nSeq++; pIcmp->icmp_checksum = checksum((USHORT*)buff, sizeof(ICMP_HDR) + 32); nRet = ::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest)); if(nRet == SOCKET_ERROR) { printf(" sendto() failed: %d \n", ::WSAGetLastError()); return -1; } nRet = ::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen); if(nRet == SOCKET_ERROR) { if(::WSAGetLastError() == WSAETIMEDOUT) { printf(" timed out\n"); continue; } printf(" recvfrom() failed: %d\n", ::WSAGetLastError()); return -1; } // 下面開始解析接收到的ICMP封包
        int nTick = ::GetTickCount(); if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR)) { printf(" Too few bytes from %s \n", ::inet_ntoa(from.sin_addr)); } // 接收到的數據中包含IP頭,IP頭大小爲20個字節,因此加20獲得ICMP頭
        ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
        if(pRecvIcmp->icmp_type != 0)    // 回顯
 { printf(" nonecho type %d recvd \n", pRecvIcmp->icmp_type); return -1; } if(pRecvIcmp->icmp_id != ::GetCurrentProcessId()) { printf(" someone else's packet! \n"); return -1; } printf(" %d bytes from %s:", nRet, inet_ntoa(from.sin_addr)); printf(" icmp_seq = %d. ", pRecvIcmp->icmp_sequence); printf(" time: %d ms", nTick - pRecvIcmp->icmp_timestamp); printf(" \n"); ::Sleep(1000); } return 0; }

執行結果:

相關文章
相關標籤/搜索