/**
// 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;
}
};