Windows平臺Ping示例源碼分析(C/C++)

//-----------------------iphdr.h-----------------------//
//源碼分析將忽略ipv6

//邊界對齊至字節
#include <pshpack1.h>

pshpack1.h爲官方頭文件,不做贅述。

// 1 -- ipv4 頭部
typedef struct ip_hdr
{
    unsigned char  ip_verlen;       // 前4位IP版本號(IPv4 或者IPv6)
                                    // 後4位頭部長度(32位,4字節)(1.1)
    unsigned char  ip_tos;          // 前3位爲優先級,後5位爲服務類型(1.2)
    unsigned short ip_totallength;  // 16位包總長度包括頭部和數據(字節)(1.3)
    unsigned short ip_id;           // 16ID標識
    unsigned short ip_offset;       // 前3位爲分段標識,後5位爲分段偏移
    unsigned char  ip_ttl;          // 該包可經過的路由器數量上限
    unsigned char  ip_protocol;     // 協議類型(TCPUDPICMPIGMP等)
    unsigned short ip_checksum;     // ipv4 頭部的校驗和
    unsigned int   ip_srcaddr;      // ipv4 源地址
    unsigned int   ip_destaddr;     // ipv4 目的地址
} IPV4_HDR, *PIPV4_HDR, FAR * LPIPV4_HDR;

此爲IPv4頭部定義。IPv4頭部一般爲20字節大小除非使用到選項位。

(1.1) 由於最大頭部的限制,ping所具有路由記錄功能在如今的網絡拓撲中無法使用(只能記錄9個ipv4地址)。
此功能由traceroute替代。

(1.2) 以上參照CCNA Study Guide的說法。在TCP/IP illustrated Volume I中前3位被忽略,後4位分別代表minimize delay, maximize throughput, maximize reliability, 和minimize monetray cost,最後1位被置0並忽略。

(1.3) 理論上ipv4可以達到的最大長度爲65536字節,除了在超級通道(hyperchannel)中出現這種最大傳輸單元外,在普通網絡中通常只允許 8192字節大小的包傳輸。TCP爲流協議沒有大小限制,UDP最大包爲512字節。一臺主機一次可接收的最大數據包爲576字節。

// 2 -- ipv4 選項頭部
typedef struct ipv4_option_hdr
{
    unsigned char   opt_code;          // ipv4 選項頭類型
    unsigned char   opt_len;           // ipv4 選項頭長度
    unsigned char   opt_ptr;           // ipv4 選項頭指針
    unsigned long   opt_addr[9];       // ipv4 9個地址列表(2.1)
} IPV4_OPTION_HDR, *PIPV4_OPTION_HDR, FAR *LPIPV4_OPTION_HDR;

(2.1) 參照(1.1)

// 3 -- icmp 頭部
typedef struct icmp_hdr
{
    unsigned char   icmp_type;            // icmp 類型
    unsigned char   icmp_code;            // ipv4 碼
    unsigned short  icmp_checksum;        // icmp 頭部及數據校驗和
    unsigned short  icmp_id;              // icmp id標識(3.1)
    unsigned short  icmp_sequence;        // icmp 序列號,請求迴應消息對
} ICMP_HDR, *PICMP_HDR, FAR *LPICMP_HDR;

(3.1) id標識一般爲發送icmp回顯請求的進程號

// 4 -- udp 頭部(此頭部未在程序中用到)
typedef struct udp_hdr
{
    unsigned short src_portno;            // 源端口
    unsigned short dst_portno;            // 目的端口
    unsigned short udp_length;            // udp 包總長度(字節)
    unsigned short udp_checksum;          // udp 頭部以及數據校驗和(4.1)
} UDP_HDR, *PUDP_HDR;

(4.1) UDP,TCP校驗和都包括頭部和數據。IP校驗和只涉及頭部。UDP校驗和可選,TCP爲強制。
// 5 -- ipv4  路徑記錄宏
#define IP_RECORD_ROUTE     0x7            

// icmp 類型和碼(5.1)
#define ICMPV4_ECHO_REQUEST_TYPE   8        // icmp  回顯請求類型
#define ICMPV4_ECHO_REQUEST_CODE   0        // icmp  回顯請求碼
#define ICMPV4_ECHO_REPLY_TYPE     0        // icmp  回顯迴應類型
#define ICMPV4_ECHO_REPLY_CODE     0        // icmp  回顯迴應碼
#define ICMPV4_MINIMUM_HEADER      8        // icmp  最小頭部

(5.1) 參照TCP/IP Illustrated : Volume 1 Chapter 6 ICMP   ICMP 消息類型

// 恢復默認對齊方式
#include <poppack.h>

//-----------------------resolve.h---------------------//

#ifndef _RESOLVE_H_
#define _RESOLVE_H_
// 在C++編譯器中以C語言的方式編譯
#ifdef _cplusplus
extern "C" {
#endif
 
int              PrintAddress(SOCKADDR *sa, int salen);
int              FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen);
int              ReverseLookup(SOCKADDR *sa, int salen, char *namebuf, int namebuflen);
struct addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto);
 
#ifdef _cplusplus
}
#endif
 
#endif

//-----------------------resolve.cpp---------------------//

// 6
#include <winsock2.h>              // socket 標準頭文件
#include <ws2tcpip.h>              // TCP/IP實現相關(6.1)
#include <strsafe.h>               // 提供安全的字符串操作(6.2)
#include <stdio.h>
#include <stdlib.h>
 
#include "resolve.h"

(6.1) 此頭文件提供 getnameinfo,getaddrinfo函數。

(6.2) StringCchPrintf, StringCchCopy 具有相對於printf 和strcpy函數更多的緩衝區安全機制。
// 7
int PrintAddress(SOCKADDR *sa, int salen)
{
    char    host[NI_MAXHOST],
            serv[NI_MAXSERV];
    int     hostlen = NI_MAXHOST,
            servlen = NI_MAXSERV,
            rc;
 
    rc = getnameinfo(              // 提供協議無關的名字解析(7.1)
            sa,
            salen,
            host,
            hostlen,
            serv,
            servlen,
            NI_NUMERICHOST | NI_NUMERICSERV
            );
    if (rc != 0)
    {
        fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
        return rc;
    }
    if (strcmp(serv, "0") != 0)
    {
        if (sa->sa_family == AF_INET)
            printf("[%s]:%s", host, serv);
        else
            printf("%s:%s", host, serv);
    }
    else
        printf("%s", host);
 
    return NO_ERROR;
}

(7.1) host 缺省返回爲一個在網絡上的完整域名。serv返回爲一個端口服務名。NI_NUMBERICHOST | NI_NUMBERICSERV 意味着返回以數字形式表示的host(IP地址)和serv(端口號)。

此函數與ResolveAddress函數的區別爲此函數將addrinfo結構中sockaddr翻譯成爲可讀信息。

int FormatAddress(SOCKADDR *sa, int salen, char *addrbuf, int addrbuflen)
{
    char    host[NI_MAXHOST],
            serv[NI_MAXSERV];
    int     hostlen = NI_MAXHOST,
            servlen = NI_MAXSERV,
            rc;
    HRESULT hRet;
 
    rc = getnameinfo(
            sa,
            salen,
            host,
            hostlen,
            serv,
            servlen,
            NI_NUMERICHOST | NI_NUMERICSERV
            );
    if (rc != 0)
    {
        fprintf(stderr, "%s: getnameinfo failed: %d\n", __FILE__, rc);
        return rc;
    }
 
    if ( (strlen(host) + strlen(serv) + 1) > (unsigned)addrbuflen)
        return WSAEFAULT;
 
    addrbuf[0] = '\0';
 
    if (sa->sa_family == AF_INET)
    {
        if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "%s:%s", host, serv)))
        {
            fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
            return (int)hRet;
        }
    }
    else if (sa->sa_family == AF_INET6)
    {
        if(FAILED(hRet = StringCchPrintf(addrbuf, addrbuflen, "[%s]:%s", host, serv)))
        {
            fprintf(stderr,"%s StringCchPrintf failed: 0x%x\n",__FILE__,hRet);
            return (int)hRet;
        }
    }
    return NO_ERROR;
}

以上代碼將解析出的IP地址和端口號以 X:X的形式存放入字符串。

struct addrinfo *ResolveAddress(char *addr, char *port, int af, int type, int proto)
{
    struct addrinfo hints,
    *res = NULL;
    int             rc;
 
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags  = ((addr) ? 0 : AI_PASSIVE);
    hints.ai_family = af;
    hints.ai_socktype = type;
    hints.ai_protocol = proto;
 
    rc = getaddrinfo(
            addr,
            port,
           &hints,
           &res
            );
    if (rc != 0)
    {
        fprintf(stderr, "Invalid address %s, getaddrinfo failed: %d\n", addr, rc);
        return NULL;
    }
    return res;
}

以上代碼解析出域名將地址信息存入addrinfo結構。

int ReverseLookup(SOCKADDR *sa, int salen, char *buf, int buflen)
{
    char    host[NI_MAXHOST];
    int     hostlen=NI_MAXHOST,
            rc;
    HRESULT hRet;
   
    rc = getnameinfo(
            sa,
            salen,
            host,
            hostlen,
            NULL,
            0,
            0
            );
    if (rc != 0)
    {
        fprintf(stderr, "getnameinfo failed: %d\n", rc);
        return rc;
    }
 
    buf[0] = '\0';
    if(FAILED(hRet = StringCchCopy(buf, buflen, host)))
    {
        fprintf(stderr,"StringCchCopy failed: 0x%x\n",hRet);
        return (int)hRet;
    }
 
    return NO_ERROR;
}

DNS逆向查找(此函數未被調用過)。  此函數和PrintAddress 函數功能相近。

//-----------------------ping.cpp---------------------//

// 64位架構
#ifdef _IA64_
#pragma warning (disable: 4267)
#endif
// 此宏定義使windows.h剔除部分頭文件,加快編譯速度
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
 
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include "resolve.h"
#include "iphdr.h"
 
#define DEFAULT_DATA_SIZE      32       // icmp 數據段大小
#define DEFAULT_SEND_COUNT     4        // ping 的次數
#define DEFAULT_RECV_TIMEOUT   6000     // 接收超時
#define DEFAULT_TTL            128      // 最大跳站術
#define MAX_RECV_BUF_LEN       0xFFFF   // 最大接收緩衝區大小
 
int   gAddressFamily=AF_UNSPEC,        
      gProtocol=IPPROTO_ICMP,          
      gTtl=DEFAULT_TTL;                
int   gDataSize=DEFAULT_DATA_SIZE;     
BOOL  bRecordRoute=FALSE;               //  是否記錄路由
char *gDestination=NULL,               
      recvbuf[MAX_RECV_BUF_LEN];       
int   recvbuflen = MAX_RECV_BUF_LEN; 

//  ping 命令使用方法
void usage(char *progname)
{
    printf("usage: %s [options] <host> \n", progname);
    printf("        host        Remote machine to ping\n");
    printf("        options: \n");
    printf("            -a 4|6       Address family (default: AF_UNSPEC)\n");
    printf("            -i ttl       Time to live (default: 128) \n");
    printf("            -l bytes     Amount of data to send (default: 32) \n");
    printf("            -r           Record route (IPv4 only)\n");
 
    return;
}
//  初始話icmp頭部
void InitIcmpHeader(char *buf, int datasize)
{
    ICMP_HDR   *icmp_hdr=NULL;
    char       *datapart=NULL;
    //  詳見icmp頭部定義
    icmp_hdr = (ICMP_HDR *)buf;
    icmp_hdr->icmp_type     = ICMPV4_ECHO_REQUEST_TYPE;  
    icmp_hdr->icmp_code     = ICMPV4_ECHO_REQUEST_CODE;
    icmp_hdr->icmp_id       = (USHORT)GetCurrentProcessId();  //進程號
    icmp_hdr->icmp_checksum = 0;   //  序列號未定義,校驗和未計算
    icmp_hdr->icmp_sequence = 0;   //  序列號置空
 
    datapart = buf + sizeof(ICMP_HDR);   //  指針移至數據段頭
    memset(datapart, 'E', datasize);     //  填充數據段
}
// 計算校驗和
USHORT checksum(USHORT *buffer, int size)
{
    unsigned long cksum=0;
 
    while (size > 1)
    {
        cksum += *buffer++;
        size -= sizeof(USHORT);
    }
    if (size)
    {
        cksum += *(UCHAR*)buffer;
    }
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >>16);
    return (USHORT)(~cksum);
}
// 傳參解析
BOOL ValidateArgs(int argc, char **argv)
{
    int                i;
    BOOL               isValid = FALSE;
 
    for(i=1; i < argc ;i++)
    {
        if ((argv[i][0] == '-') || (argv[i][0] == '/'))
        {
            switch (tolower(argv[i][1]))
            {
                case 'a':       
                    if (i+1 >= argc)
                    {
                        usage(argv[0]);
                        goto CLEANUP;
                    }
                    if (argv[i+1][0] == '4')
                        gAddressFamily = AF_INET;
                    else if (argv[i+1][0] == '6')
                        gAddressFamily = AF_INET6;
                    else
                    {
                        usage(argv[0]);
                        goto CLEANUP;
                    }
 
                    i++;
                    break;
                case 'i':        // 設置最大跳值
                    if (i+1 >= argc)
                    {
                        usage(argv[0]);
                        goto CLEANUP;
                    }
 
                    gTtl = atoi(argv[++i]);
                    break;
                case 'l':        //  icmp數據段大小設置
                    if (i+1 >= argc)
                    {
                        usage(argv[0]);
                        goto CLEANUP;
                    }
 
                    gDataSize = atoi(argv[++i]);
                    break;
                case 'r':        //  記錄路由選項
                    bRecordRoute = TRUE;
                    break;
                default:
                    usage(argv[0]);
                    goto CLEANUP;
            }
        }
        else
        {
            gDestination = argv[i];
        }
    }
 
    isValid = TRUE;
 
CLEANUP:   
    return isValid;
}
//  設置icmp序列號
void SetIcmpSequence(char *buf)
{
    ULONG    sequence=0;
 
    sequence = GetTickCount();
    if (gAddressFamily == AF_INET)
    {
        ICMP_HDR    *icmpv4=NULL;
 
        icmpv4 = (ICMP_HDR *)buf;
 
        icmpv4->icmp_sequence = (USHORT)sequence;
    }
    else if (gAddressFamily == AF_INET6)
    {
        ICMPV6_HDR          *icmpv6=NULL;
        ICMPV6_ECHO_REQUEST *req6=NULL;
 
        icmpv6 = (ICMPV6_HDR *)buf;
        req6   = (ICMPV6_ECHO_REQUEST *)(buf + sizeof(ICMPV6_HDR));
 
        req6->icmp6_echo_sequence = (USHORT)sequence;
    }
}
//  計算icmp校驗和
void ComputeIcmpChecksum(SOCKET s, char *buf, int packetlen, struct addrinfo *dest)
{
    if (gAddressFamily == AF_INET)
    {
        ICMP_HDR    *icmpv4=NULL;
 
        icmpv4 = (ICMP_HDR *)buf;
        icmpv4->icmp_checksum = 0;
        icmpv4->icmp_checksum = checksum((USHORT *)buf, packetlen);
    }
    else if (gAddressFamily == AF_INET6)
    {
        ICMPV6_HDR  *icmpv6=NULL;
 
        icmpv6 = (ICMPV6_HDR *)buf;
        icmpv6->icmp6_checksum = 0;
        icmpv6->icmp6_checksum = ComputeIcmp6PseudoHeaderChecksum(
                s,
                buf,
                packetlen,
                dest
                );
    }
}
//  發佈異步接收
int PostRecvfrom(SOCKET s, char *buf, int buflen, SOCKADDR *from, int *fromlen, WSAOVERLAPPED *ol)
{
    WSABUF  wbuf;
    DWORD   flags,
            bytes;
    int     rc;
 
    wbuf.buf = buf;
    wbuf.len = buflen;
 
    flags = 0;
 
    rc = WSARecvFrom(         //  通過重疊IO實現異步接收
            s,
           &wbuf,
            1,
           &bytes,
           &flags,
            from,
            fromlen,
            ol,
            NULL
            );
    if (rc == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
            fprintf(stderr, "WSARecvFrom failed: %d\n", WSAGetLastError());
            return SOCKET_ERROR;
        }
    }
    return NO_ERROR;
}

//  8
void PrintPayload(char *buf, int bytes)
{
    int     hdrlen=0,
            routes=0,
            i;
 
    UNREFERENCED_PARAMETER(bytes);
 
    if (gAddressFamily == AF_INET)
    {
        SOCKADDR_IN      hop;
        IPV4_OPTION_HDR *v4opt=NULL;
        IPV4_HDR        *v4hdr=NULL;
 
        hop.sin_family = (USHORT)gAddressFamily;
        hop.sin_port   = 0;
 
        v4hdr = (IPV4_HDR *)buf;
        hdrlen = (v4hdr->ip_verlen & 0x0F) * 4// 計算IP頭部長度(8.1)

        if (hdrlen > sizeof(IPV4_HDR))   // 如果長度大於無選項IP頭部長度
        {
                               // 選項頭部指針
            v4opt = (IPV4_OPTION_HDR *)(buf + sizeof(IPV4_HDR));
                               //計算路由數(8.2)
            routes = (v4opt->opt_ptr / sizeof(ULONG)) - 1;
            for(i=0; i < routes ;i++)
            {
                hop.sin_addr.s_addr = v4opt->opt_addr[i];
                if (i == 0)
                    printf("    Route: ");
                else
                    printf("           ");
                PrintAddress((SOCKADDR *)&hop, sizeof(hop));
                                if (i < routes-1)
                    printf(" ->\n");
                else
                    printf("\n");
            }
        }
    }
    return;
}

(8.1) (v4hdr->ip_verlen & 0x0F) * ip_verlen前4位爲版本號,後四位爲頭部長度(4字節一記,因此要乘以4)。ip_verlen & 0x0F, 前四位屏蔽,後四位保留。
(8.2)  v4opt->opt_ptr 指向下一個可用地址,因此需要減一。

// 設置最大跳站數
int SetTtl(SOCKET s, int ttl)
{
    int     optlevel = 0,
            option = 0,
            rc;
 
    rc = NO_ERROR;
    if (gAddressFamily == AF_INET)
    {
        optlevel = IPPROTO_IP;
        option   = IP_TTL;
    }
    else if (gAddressFamily == AF_INET6)
    {
        optlevel = IPPROTO_IPV6;
        option   = IPV6_UNICAST_HOPS;
    }
    else
    {
        rc = SOCKET_ERROR;
    }
    if (rc == NO_ERROR)
    {
        rc = setsockopt(
                s,
                optlevel,
                option,
                (char *)&ttl,
                sizeof(ttl)
                );
    }
    if (rc == SOCKET_ERROR)
    {
        fprintf(stderr, "SetTtl: setsockopt failed: %d\n", WSAGetLastError());
    }
    return rc;
}

// 9
int __cdecl main(int argc, char **argv)
{
    WSADATA            wsd;
    WSAOVERLAPPED      recvol;        // 重疊 IO
    SOCKET             s=INVALID_SOCKET;
    char              *icmpbuf=NULL;
    struct addrinfo   *dest=NULL,
                      *local=NULL;
    IPV4_OPTION_HDR    ipopt;
    SOCKADDR_STORAGE   from;          // socket地址存儲結構(9.1)
    DWORD              bytes,
                       flags;
    int                packetlen=0,
                       fromlen,
                       time=0,
                       rc,
                       i,
                       status = 0;
 
    recvol.hEvent = WSA_INVALID_EVENT;
 
    //  分析輸入參數
    if (ValidateArgs(argc, argv) == FALSE)
    {
        status = -1;
        goto EXIT;
    }
 
    // socket模塊啓動初始化
    if ((rc = WSAStartup(MAKEWORD(2,2), &wsd)) != 0)
    {
        printf("WSAStartup() failed: %d\n", rc);
        status = -1;
        goto EXIT;
    }
 
    //  解析目的地址
    dest = ResolveAddress(
            gDestination,
            "0",
            gAddressFamily,
            0,
            0
            );
    if (dest == NULL)
    {
        printf("bad name %s\n", gDestination);
        status = -1;
        goto CLEANUP;
    }
    gAddressFamily = dest->ai_family;
 
    if (gAddressFamily == AF_INET)
        gProtocol = IPPROTO_ICMP;
    else if (gAddressFamily == AF_INET6)
        gProtocol = IPPROTO_ICMP6;
 
    // 獲得本地地址,綁定使用
    local = ResolveAddress(
            NULL,
            "0",
            gAddressFamily,
            0,
            0
            );
    if (local == NULL)
    {
        printf("Unable to obtain the bind address!\n");
        status = -1;
        goto CLEANUP;
    }
 
    //創建Raw套接字,protocol = IPPROTO_ICMP
    s = socket(gAddressFamily, SOCK_RAW, gProtocol);           
    if (s == INVALID_SOCKET)
    {
        printf("socket failed: %d\n", WSAGetLastError());
        status = -1;
        goto CLEANUP;
    }
 
    SetTtl(s, gTtl); //設置最大跳站數爲128
 
    if (gAddressFamily == AF_INET)
        packetlen += sizeof(ICMP_HDR);
    else if (gAddressFamily == AF_INET6)
        packetlen += sizeof(ICMPV6_HDR) + sizeof(ICMPV6_ECHO_REQUEST);
 
    // packetlen 爲  數據長度+ICMP頭部長度
    packetlen += gDataSize;
 
    // 分配空間存儲ICMP請求(9.2)
    icmpbuf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, packetlen);
    if (icmpbuf == NULL)
    {
        fprintf(stderr, "HeapAlloc failed: %d\n", GetLastError());
        status = -1;
        goto CLEANUP;
    }
 
    // 初始化 ICMP 頭部
    if (gAddressFamily == AF_INET)
    {
        if (bRecordRoute) // 如有路由記錄功能,初始化選項數據段
        {
            ZeroMemory(&ipopt, sizeof(ipopt));
            ipopt.opt_code = IP_RECORD_ROUTE; // 路由記錄選項
            ipopt.opt_ptr  = 4;               // 指向可用的地址,每個地址佔用4字節,此指針指向第一個可用的存儲偏移地址
            ipopt.opt_len  = 39;              // 選項數據段長度
 
            rc = setsockopt(s, IPPROTO_IP, IP_OPTIONS,
                    (char *)&ipopt, sizeof(ipopt));
            if (rc == SOCKET_ERROR)
            {
                fprintf(stderr, "setsockopt(IP_OPTIONS) failed: %d\n", WSAGetLastError());
                status = -1;
                goto CLEANUP;
            }
        }
 
        InitIcmpHeader(icmpbuf, gDataSize);
    }
    else if (gAddressFamily == AF_INET6)
    {
        InitIcmp6Header(icmpbuf, gDataSize);
    }
 
    // 綁定地址,此套接字可獲得外部單元向這個地址發送的數據
    rc = bind(s, local->ai_addr, (int)local->ai_addrlen);
    if (rc == SOCKET_ERROR)
    {
        fprintf(stderr, "bind failed: %d\n", WSAGetLastError());
        status = -1;
        goto CLEANUP;
    }
 
    // 建立接收操作
    memset(&recvol, 0, sizeof(recvol));
    recvol.hEvent = WSACreateEvent();    // 事件初始化
    if (recvol.hEvent == WSA_INVALID_EVENT)
    {
        fprintf(stderr, "WSACreateEvent failed: %d\n", WSAGetLastError());
        status = -1;
        goto CLEANUP;
    }
 
    //  異步接收
    fromlen = sizeof(from);
    PostRecvfrom(s, recvbuf, recvbuflen, (SOCKADDR *)&from, &fromlen, &recvol);
 
    printf("\nPinging ");
    PrintAddress(dest->ai_addr, (int)dest->ai_addrlen);
    printf(" with %d bytes of data\n\n", gDataSize);

    // ping 4 次
    for(i=0; i < DEFAULT_SEND_COUNT ;i++)
    {
        // 設置序列號並計算校驗和
        SetIcmpSequence(icmpbuf);
        ComputeIcmpChecksum(s, icmpbuf, packetlen, dest);
 
        time = GetTickCount();
        rc = sendto(      // 發送icmp請求
                s,
                icmpbuf,
                packetlen,
                0,
                dest->ai_addr,
                (int)dest->ai_addrlen
                );
        if (rc == SOCKET_ERROR)
        {
            fprintf(stderr, "sendto failed: %d\n", WSAGetLastError());
            status = -1;
            goto CLEANUP;
        }
 
        // 等待ICMP回覆(9.3)
        rc = WaitForSingleObject((HANDLE)recvol.hEvent, DEFAULT_RECV_TIMEOUT);
        if (rc == WAIT_FAILED)
        {
            fprintf(stderr, "WaitForSingleObject failed: %d\n", GetLastError());
            status = -1;
            goto CLEANUP;
        }
        else if (rc == WAIT_TIMEOUT)
        {
            printf("Request timed out.\n");
        }
        else
        {
                   // 收到ICMP回覆
            rc = WSAGetOverlappedResult(
                   s,
                   &recvol,
                   &bytes,
                    FALSE,
                   &flags
                    );
            if (rc == FALSE)
            {
                fprintf(stderr, "WSAGetOverlappedResult failed: %d\n", WSAGetLastError());
            }
            time = GetTickCount() - time;
 
            WSAResetEvent(recvol.hEvent);
 
            printf("Reply from ");
            PrintAddress((SOCKADDR *)&from, fromlen);
            if (time == 0)
                printf(": bytes=%d time<1ms TTL=%d\n", gDataSize, gTtl);
            else
                printf(": bytes=%d time=%dms TTL=%d\n", gDataSize, time, gTtl);
 
            PrintPayload(recvbuf, bytes);
 
            if (i < DEFAULT_SEND_COUNT - 1)
            {
                fromlen = sizeof(from);
                PostRecvfrom(s, recvbuf, recvbuflen, (SOCKADDR *)&from, &fromlen, &recvol);
            }
        }
        Sleep(1000);
    }
 
CLEANUP:
   
    if (dest)
         freeaddrinfo(dest);
    if (local)
         freeaddrinfo(local);
    if (s != INVALID_SOCKET)
        closesocket(s);
    if (recvol.hEvent != WSA_INVALID_EVENT)
        WSACloseEvent(recvol.hEvent);
    if (icmpbuf)
        HeapFree(GetProcessHeap(), 0, icmpbuf);
 
    WSACleanup();
 
EXIT:
 
    system("pause");
 
    return status;
}

(9.1) sockaddr 以及 sockaddr_in比較:這兩個結構大小相同全爲16字節,可相互轉化。唯一區別是sockaddr_in將內容分得更細,sockaddr結構一般作爲參數帶入,sockaddr_in結構在填值時使用。
struct sockaddr {
        u_short sa_family;             // 2字節
        char    sa_data[14];           // 14字節
};
 
typedef struct addrinfo
{
int                 ai_flags;      
int                 ai_family;      
    int                 ai_socktype;   
    int                 ai_protocol;   
    size_t              ai_addrlen;    
    char *              ai_canonname;  
    struct sockaddr *   ai_addr;      
    struct addrinfo *   ai_next;      
};
 
struct sockaddr_in {
        short   sin_family;         // 2字節
        u_short sin_port;           // 2字節
        struct  in_addr sin_addr;   // 4字節
        char    sin_zero[8];        // 8字節填充
};

struct in_addr {      // 4字節
        union {
                struct { u_char s_b1,s_b2,s_b3,s_b4; }                     S_un_b;
                struct { u_short s_w1,s_w2; } S_un_w;
                u_long S_addr;
        } S_un;
};

(9.2)  HeapAlloc 和 malloc的功能一樣,都是在堆上面分配空間。HeapAlloc爲Windows SDK中提供的函數,malloc是anci C中的函數。後者更有可移植性。

(9.3) 此程序首先發起異步接收後再發送ICMP請求包可以保證發送端不會漏掉任何ICMP迴應。UDP協議下先發送後接收的方式會因爲接收端運行速度慢的問題導致丟包。

本文出自 “一網無垠” 博客,請務必保留此出處http://unliminet.blog.51cto.com/380895/77717

發佈了25 篇原創文章 · 獲贊 11 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章