c语言模拟icmp发送报文(tracert命令)

本文档展示了如何使用C++编写代码来发送ICMP回显请求,并接收和解码ICMP响应,包括ICMP_ECHO_REPLY和ICMP_TIMEOUT。代码中包含了计算校验和、设置套接字超时以及处理IP报头和ICMP报头的细节。程序用于网络路径探测,通过TTL递增的方式跟踪到目标主机,输出沿途经过的IP地址和往返时间。
摘要由CSDN通过智能技术生成
#include <iostream>
#include <winsock2.h>       
#include <ws2tcpip.h>
#include <Winsock2.h>
#include <ws2ipdef.h>
using namespace std;

#pragma comment(lib, "Ws2_32.lib")

//IP报头
typedef struct IP_HEADER
{
    unsigned char hdr_len : 4;       //4位头部长度 一位4个字节,,最多15*4个字节(可选项)
    unsigned char version : 4;       //4位版本号
    unsigned char tos;             //8位服务类型
    unsigned short total_len;      //16位总长度
    unsigned short identifier;     //16位标识符
    unsigned short frag_and_flags; //3位标志加13位片偏移
    unsigned char ttl;             //8位生存时间
    unsigned char protocol;        //8位上层协议号
    unsigned short checksum;       //16位校验和
    unsigned long sourceIP;        //32位源IP地址
    unsigned long destIP;          //32位目的IP地址
} IP_HEADER;

//ICMP报头
typedef struct ICMP_HEADER
{
    BYTE type;    //8位类型字段
    BYTE code;    //8位代码字段
    USHORT cksum; //16位校验和
    USHORT id;    //16位标识符
    USHORT seq;   //16位序列号
} ICMP_HEADER;

//报文解码结构
typedef struct DECODE_RESULT
{
    USHORT usSeqNo;        //序列号
    DWORD dwRoundTripTime; //往返时间
    in_addr dwIPaddr;      //返回报文的IP地址
}DECODE_RESULT;


//计算网际校验和函数
USHORT checksum(USHORT* pBuf, int iSize)
{
    unsigned long cksum = 0;
    while (iSize > 1)
    {
        cksum += *pBuf++;
        iSize -= sizeof(USHORT);
    }
    if (iSize)//如果 iSize 为正,即为奇数个字节
    {
        cksum += *(UCHAR*)pBuf; //则在末尾补上一个字节,使之有偶数个字节
    }
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    return (USHORT)(~cksum);
}

//对数据包进行解码
BOOL DecodeIcmpResponse(char* pBuf, int iPacketSize, DECODE_RESULT& DecodeResult,
    BYTE ICMP_ECHO_REPLY, BYTE  ICMP_TIMEOUT)
{
    //检查数据报大小的合法性
    IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
    int iIpHdrLen = pIpHdr->hdr_len * 4;    //ip报头的长度是以4字节为单位的


    //若数据包大小 小于 IP报头 + ICMP报头,则数据报大小不合法
    if (iPacketSize < (int)(iIpHdrLen + sizeof(ICMP_HEADER)))
        return FALSE;

    //根据ICMP报文类型提取ID字段和序列号字段
    ICMP_HEADER* pIcmpHdr = (ICMP_HEADER*)(pBuf + iIpHdrLen);//ICMP报头 = 接收到的缓冲数据 + IP报头
    USHORT usID, usSquNo;


    if (pIcmpHdr->type == ICMP_ECHO_REPLY)    //ICMP回显应答报文
    {
        usID = pIcmpHdr->id;        //报文ID
        usSquNo = pIcmpHdr->seq;    //报文序列号
    }
    else if (pIcmpHdr->type == ICMP_TIMEOUT)//ICMP超时差错报文
    {
        /*以下的操作和收到的ICMP超时报文有关*/
        char* pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER);
        int iInnerIPHdrLen = ((IP_HEADER*)pInnerIpHdr)->hdr_len * 4; 
        ICMP_HEADER* pInnerIcmpHdr = (ICMP_HEADER*)(pInnerIpHdr + iInnerIPHdrLen);

        usID = pInnerIcmpHdr->id;        //报文ID
        usSquNo = pInnerIcmpHdr->seq;    //序列号
    }
    else
    {
        return false;
    }

    //检查ID和序列号以确定收到期待数据报
    if (usID != (USHORT)GetCurrentProcessId() || usSquNo != DecodeResult.usSeqNo)
    {
        return false;
    }

    //记录IP地址并计算往返时间
    DecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
    DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;

    //处理正确收到的ICMP数据报
    if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT)
    {
        //输出往返时间信息
        if (DecodeResult.dwRoundTripTime)
            cout << "      " << DecodeResult.dwRoundTripTime << "ms" << flush;
        else
            cout << "      " << "<1ms" << flush;
    }
    return true;
}

int main()
{
    WSADATA wsa;
    if(WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        return -1;
    }

    char IpAddress[255];
    cout << "请输入一个IP地址或域名:";
    cin >> IpAddress;


    //得到IP地址,字符数字转整形
    u_long ulDestIP = inet_addr(IpAddress);

    //转换不成功时按域名解析
    if (ulDestIP == INADDR_NONE)
    {
        /*通过IP地址获取域名*/
        hostent* pHostent = gethostbyname(IpAddress);
        if(pHostent)
        {
            ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;
        }
        else
        {
            cout << "输入的IP地址或域名无效!" << endl;
            WSACleanup();
            return 0;
        }
    }
    cout << "Tracing roote to " << IpAddress << " with a maximum of 30 hops.\n" << endl;

    /*填充目的端socket地址*/
    sockaddr_in destSockAddr;
    ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
    destSockAddr.sin_family = AF_INET;
    destSockAddr.sin_addr.s_addr = ulDestIP;


    //创建原始套接字
    SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);

    //超时时间
    int iTimeout = 3000;

    //设置接收超时时间
    setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&iTimeout, sizeof(iTimeout));

    //设置发送超时时间
    setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&iTimeout, sizeof(iTimeout));

    //构造ICMP回显请求消息,并以TTL递增的顺序发送报文
    //ICMP类型字段
    const BYTE ICMP_ECHO_REQUEST = 8;    //请求回显
    const BYTE ICMP_ECHO_REPLY = 0;    //回显应答
    const BYTE ICMP_TIMEOUT = 11;   //传输超时

    //其他常量定义
    const int DEF_ICMP_DATA_SIZE = 32;    //ICMP报文默认数据字段长度
    const int MAX_ICMP_PACKET_SIZE = 1024;  //ICMP报文最大长度(包括报头)
    const DWORD DEF_ICMP_TIMEOUT = 3000;  //回显应答超时时间
    const int DEF_MAX_HOP = 30;    //最大跳站数

    //填充ICMP报文中每次发送时不变的字段
    char IcmpSendBuf[sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE];//发送缓冲区
    memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));               //初始化发送缓冲区
    char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];                      //接收缓冲区
    memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));               //初始化接收缓冲区


    /*填写ICMP头,回显请求*/
    ICMP_HEADER* pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
    pIcmpHeader->type = ICMP_ECHO_REQUEST; 
    pIcmpHeader->code = 0; 
    /*ID字段为当前进程号*/
    pIcmpHeader->id = (USHORT)GetCurrentProcessId();
    memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段


    USHORT usSeqNo = 0;                 //ICMP报文序列号
    int iTTL = 1;                       //TTL初始值为1
    BOOL bReachDestHost = FALSE;        //循环退出标志
    int iMaxHot = DEF_MAX_HOP;          //循环的最大次数
    DECODE_RESULT DecodeResult;         //传递给报文解码函数的结构化参数
    while (!bReachDestHost && iMaxHot--)
    {
        //设置IP报头的TTL字段
        setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char*)&iTTL, sizeof(iTTL));
        cout << iTTL << flush;    //输出当前序号,flush表示将缓冲区的内容马上送进cout,把输出缓冲区刷新

        //填充ICMP报文中每次发送变化的字段
        ((ICMP_HEADER*)IcmpSendBuf)->cksum = 0;                   //校验和先置为0
        ((ICMP_HEADER*)IcmpSendBuf)->seq = htons(usSeqNo++);      //填充序列号
        ((ICMP_HEADER*)IcmpSendBuf)->cksum =
            checksum((USHORT*)IcmpSendBuf, sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE); //计算校验和


        //记录序列号和当前时间
        DecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;    //当前序号
        DecodeResult.dwRoundTripTime = GetTickCount();                          //当前时间


        //发送TCP回显请求信息
        sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr*)&destSockAddr, sizeof(destSockAddr));


        //接收ICMP差错报文并进行解析处理
        sockaddr_in from;           //对端socket地址
        int iFromLen = sizeof(from);//地址结构大小
        int iReadDataLen;           //接收数据长度
        while (1)
        {
            //接收数据
            iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr*)&from, &iFromLen);
            if (iReadDataLen != SOCKET_ERROR)//有数据到达
            {
                //对数据包进行解码
                if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, ICMP_TIMEOUT))
                {
                    //到达目的地,退出循环
                    if (DecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
                        bReachDestHost = true;
                    //输出IP地址
                    cout << '\t' << inet_ntoa(DecodeResult.dwIPaddr) << endl;
                    break;
                }
            }
            else if (WSAGetLastError() == WSAETIMEDOUT)    //接收超时,输出*号
            {
                cout << "         *" << '\t' << "Request timed out." << endl;
                break;
            }
            else
            {
                break;
            }
        }
        iTTL++;    //递增TTL值
    }
}

ICMP报文会添加一个IP头,然后发到网络当中。

运行结果:
在这里插入图片描述
图为,ICMP报文当中的TTL值减为0时,且还没达到目标主机时,对端会回复一个超时报文,结构如下所示:

参考:1:https://blog.csdn.net/u013271921/article/details/45488173

2:https://blog.csdn.net/fuming0210sc/article/details/53521574

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值