icmp实现ping
创建:2023-10-27 17:43
更新:2023-10-27 17:45

https://blog.csdn.net/FlushHip/article/details/83993826

/**

// https://www.wlgly.net/post-84.html
// https://www.51cto.com/article/616746.html

## ICMP 报文格式

+0------7-------15---------------31
|  Type | Code  |    Checksum    |
+--------------------------------+
|          Message Body          |
|        (Variable length)       |
+--------------------------------+

## ICMP 报文格式解释

字段            长度      含义
Type            1字节     报文类型,用来标识报文,Type字段的取值和含义如下表1所示。
Code            1字节     代码,提供报文类型的进一步信息,Code字段的取值和含义如下表1所示。
Checksum        2字节     校验和,使用和IP相同的加法校验和算法,但是ICMP校验和仅覆盖ICMP报文。
Message Body    可变      字段的长度和内容,取决于消息的类型和代码,请参见下表1。


## ICMP消息类型代码

其中,最后一个字段的长度和内容,取决于消息的类型和代码。对应的列表如下:
表1 ICMP消息类型代码对应表

类型Type    代码Code   描述
0            0           回显应答(ping应答)
3            0           网络不可达
3            1           主机不可达
3            2           协议不可达
3            3           端口不可达
3            4           需要进行分片但设置不分片比特
3            5           源站选路失败
3            6           目的网络不认识
3            7           目的主机不认识
3            8           源主机被隔离(作废不用)
3            9           目的网络被强制禁止
3            10          目的主机被强制禁止
3            11          由于TOS,网络不可达
3            12          由于TOS,主机不可达
3            13          由于过滤,通信被强制禁止
3            14          主机越权
3            15          优先权中止生效
4            0           源端被关闭
5            0           对网络重定向
5            1           对主机重定向
5            2           对服务类型和网络重定向
5            3           对服务类型和主机重定向
8            0           请求回显(ping请求)
9            0           路由器通告
10            0           路由器请求告
11            0           传输期间生存时间为0
11            1           在数据报组装期间生存时间为0
12            0           坏的IP首部
12            1           缺少必须的选项
13            0           时间戳请求(作废不用)
14            0           时间戳应答(作废不用)
15            0           信息请求(作废不用)
16            0           信息应答(作废不用)
17            0           地址掩码请求
18            0           地址掩码应答

*/

#ifdef _WIN32

#include <sys/time.h>
#include <time.h>

#include <winsock2.h>
struct WindowsSocketLibInit {
    WindowsSocketLibInit() {
        WSADATA wsaData;
        WORD sockVersion = MAKEWORD(2, 2);
        WSAStartup(sockVersion, &wsaData);
    }
    ~WindowsSocketLibInit() {
        WSACleanup();
    }
} INITSOCKETGLOBALVARIABLE;

#else

#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#endif

#include <string>
#include <limits>

bool ping(std::string ip) {
    static unsigned INDEX = 0;

    struct IcmpHead {
        unsigned char type;
        unsigned char code;
        unsigned short checksum;
        unsigned short id;
        unsigned short seq;
    };

    int socketFd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    int timeoutTick = 1000;
    setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeoutTick, sizeof(timeoutTick));

    sockaddr_in des = {AF_INET, htons(0)};
    des.sin_addr.s_addr = inet_addr(ip.c_str());

    char buff[sizeof(IcmpHead) + 32] = {0};
    IcmpHead *pIcmpHead = (IcmpHead *)(buff);

    unsigned short id = std::rand() % (std::numeric_limits<unsigned short>::max)();

    pIcmpHead->type = 8;
    pIcmpHead->code = 0;
    pIcmpHead->id = id;
    pIcmpHead->seq = INDEX++;
    memcpy(&buff[sizeof(IcmpHead)], "FlushHip", sizeof("FlushHip"));
    pIcmpHead->checksum = [](unsigned short *buff, unsigned size) -> unsigned short {
        unsigned long ret = 0;
        for (unsigned i = 0; i < size; i += sizeof(unsigned short)) {
            ret += *buff++;
        }
        if (size & 1) {
            ret += *(unsigned char *)buff;
        }
        ret = (ret >> 16) + (ret & 0xFFFF);
        ret += ret >> 16;
        return (unsigned short)~ret;
    }((unsigned short *)buff, sizeof(buff));

    if (-1 == sendto(socketFd, buff, sizeof(buff), 0, (sockaddr *)&des, sizeof(des))) {
        return false;
    }

    char recv[1 << 10];
    int ret = recvfrom(socketFd, recv, sizeof(recv), 0, NULL, NULL);
    if (-1 == ret || ret < 20 + sizeof(IcmpHead)) {
        return false;
    }

    IcmpHead *pRecv = (IcmpHead *)(recv + 20);
    return !(pRecv->type != 0 || pRecv->id != id);
}

long systemCurTime() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    long time = 0;
    time += tv.tv_sec * 1000000;
    time += tv.tv_usec;
    return time;
}

int main(int argc, char const *argv[]) {
    if (argc < 2) {
        printf("Error: need input ip");
        return -1;
    }
    std::string ip = argv[1];
    for (int i = 0; i < 3; i++) {
        long curTime = systemCurTime();
        if (ping(ip.c_str())) {
            printf("ping %s success: %ld\n", ip.c_str(), systemCurTime() - curTime);
        } else {
            printf("ping %s failed: %ld\n", ip.c_str(), systemCurTime() - curTime);
        }
    }

    return 0;
}

利用ttl超时完成链路查询功能

#pragma once

#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <string>
#include <limits>
#include <functional>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <icmpapi.h>
struct WindowsSocketLibInit {
    WindowsSocketLibInit() {
        WSADATA wsaData;
        WORD sockVersion = MAKEWORD(2, 2);
        WSAStartup(sockVersion, &wsaData);
    }
    ~WindowsSocketLibInit() {
        WSACleanup();
    }
} INITSOCKETGLOBALVARIABLE;
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#endif

class PingUtil {
    struct IcmpHead {
        unsigned char type;
        unsigned char code;
        unsigned short checksum;
        unsigned short id;
        unsigned short seq;
    };

    static long curTime() {
        struct timeval tv;
        gettimeofday(&tv, NULL);
        long time = 0;
        time += tv.tv_sec * 1000000;
        time += tv.tv_usec;
        return time;
    }

public:
    static int ping(std::string target, int ttl, bool increase, std::function<void(int ttl, const std::string &ip, long send_time_pass, long reci_time_pass)> callback) {
        static unsigned INDEX = 0;
        int ret = 0;

        if (ttl <= 0) {
            ttl = 64;
        }

        struct hostent *host = gethostbyname(target.c_str());
        if (host == nullptr) {
            printf("get host by name failed\n");
            return -1;
        }
        char ip[32] = {0};
        inet_ntop(AF_INET, host->h_addr_list[0], ip, sizeof(ip));    // IP转化为字符串


        int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        int timeout_tick = 1000;
        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout_tick, sizeof(timeout_tick));

        sockaddr_in des = {AF_INET, htons(0)};
        des.sin_addr.s_addr = inet_addr(ip);

        int ittl = ttl - 1;
        if (increase) {    // 自增获取全链路流程
            ittl = 0;
        }

        do {
            ittl++;

            char buff[sizeof(IcmpHead) + 32] = {0};
            IcmpHead *icmp_send = (IcmpHead *)(buff);

            unsigned short id = std::rand() % (std::numeric_limits<unsigned short>::max)();
            icmp_send->type = 8;
            icmp_send->code = 0;
            icmp_send->id = id;
            icmp_send->seq = INDEX++;
            memcpy(&buff[sizeof(IcmpHead)], "Hello", sizeof("Hello"));
            icmp_send->checksum = [](unsigned short *buff, unsigned size) -> unsigned short {
                unsigned long ret = 0;
                for (unsigned i = 0; i < size; i += sizeof(unsigned short)) {
                    ret += *buff++;
                }
                if (size & 1) {
                    ret += *(unsigned char *)buff;
                }
                ret = (ret >> 16) + (ret & 0xFFFF);
                ret += ret >> 16;
                return (unsigned short)~ret;
            }((unsigned short *)buff, sizeof(buff));

            if (-1 == setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ittl, sizeof(ittl))) {
                ret = -2;
                break;
            }
            if (-1 == setsockopt(sockfd, IPPROTO_IP, IP_TTL, (char *)&ittl, sizeof(ittl))) {
                ret = -3;
                break;
            }

            long last_time = curTime();

            if (-1 == sendto(sockfd, buff, sizeof(buff), 0, (sockaddr *)&des, sizeof(des))) {
                ret = -4;
                break;
            }

            // 统计发送耗时
            long cur_time = curTime();
            long send_time_pass = cur_time - last_time;
            last_time = cur_time;

            char recv[4096] = {0};
            sockaddr_in recv_addr = {0};
            int addr_len = sizeof(recv_addr);
            int ret = recvfrom(sockfd, recv, sizeof(recv), 0, (sockaddr *)&recv_addr, &addr_len);

            // 统计接收耗时
            cur_time = curTime();
            long recv_time_pass = cur_time - last_time;
            std::string recv_ip = inet_ntoa(recv_addr.sin_addr);

            if (-1 == ret || ret < 20 + (int)sizeof(IcmpHead)) {
                callback(ittl, recv_ip, send_time_pass, recv_time_pass);
                continue;    // 接收错误,直接跳过
            }
            IcmpHead *icmp_recv = (IcmpHead *)(recv + 20);
            if (icmp_recv->type == 11) {
                callback(ittl, recv_ip, send_time_pass, recv_time_pass);
            }
            if (icmp_recv->type == 0) {
                callback(ittl, recv_ip, send_time_pass, recv_time_pass);
                ret = 1;
                break;
            }
        } while (ittl < ttl);

#ifdef _WIN32
        closesocket(sockfd);
#else
        close(sockfd);
#endif
        return ret;
    }
};