互联网控制消息协议(英文:Internet Control Message Protocol,ICMP)是互联网协议族的核心协议之一。定义在 RFC 792 文档中。
ICMP 的消息大致可以分为两类:一类是差错报文,即通知出错原因的错误消息(如 traceroute),另一类是查询报文,即用于诊断的查询消息(如 ping)。
使用 ICMP 协议的典型应用有 ping 和 traceroute(windows 上叫 tracert)。

ICMP 是在 IP 数据报的内部被传输的,紧跟着 IP 报文的首部(如果 IP 首部有可选部分,则紧跟着可选部分)

图上的 IP 首部 20 字节是在 IP 报文首部不含可选部分的情况下,若 IP 首部含可选部分,则大于 20 字节。

一、 ICMP 报文格式

所有 ICMP 报文的前 4 个字节都是一样的,但剩下的其他字节则根据报文类型的不同而不同。

  • 8位类型字段8位代码字段共同决定一种 ICMP 报文的类型。

  • 校验和的计算方法和 IP 首部校验和的计算方式相同,但 ICMP 校验和覆盖整个 ICMP 报文。

    IP 首部校验和的计算方式和原理参考:网络协议(3)--IP协议
    UDP 和 TCP 的校验和同样也都覆盖到了他们的首部和数据。

《TCP/IP 详解 卷 1:协议》在线阅读地址:http://www.52im.net/topic-tcpipvol1.html

二、实际应用

2.1 Ping

2.1.1 Ping 程序原理

大多数系统都已经在内核中内置了 ping 服务器的功能,所以不需要单独的其他进程来接收主机的 ping 请求。

windows 系统下,输入ping /?命令查看 ping 的用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
[-r count] [-s count] [[-j host-list] | [-k host-list]]
[-w timeout] [-R] [-S srcaddr] [-4] [-6] target_name

选项:
-t Ping 指定的主机,直到停止。
若要查看统计信息并继续操作 - 请键入 Control-Break;
若要停止 - 请键入 Control-C。
-a 将地址解析成主机名。
-n count 要发送的回显请求数。
-l size 发送缓冲区大小。
-f 在数据包中设置“不分段”标志(仅适用于 IPv4)。
-i TTL 生存时间。
-v TOS 服务类型(仅适用于 IPv4。该设置已不赞成使用,且
对 IP 标头中的服务字段类型没有任何影响)。
-r count 记录计数跃点的路由(仅适用于 IPv4)。
-s count 计数跃点的时间戳(仅适用于 IPv4)。
-j host-list 与主机列表一起的松散源路由(仅适用于 IPv4)。
-k host-list 与主机列表一起的严格源路由(仅适用于 IPv4)。
-w timeout 等待每次回复的超时时间(毫秒)。
-R 同样使用路由标头测试反向路由(仅适用于 IPv6)。
-S srcaddr 要使用的源地址。
-4 强制使用 IPv4。
-6 强制使用 IPv6。

其中-r参数用于记录跃点路由,类似 tracert 的功能,但他们的实现方式不一样,ping 是通过在 IP 头部“选项”字段记录经过的每个路由的 IP 来实现记录路由功能的,这种实现有个限制,就是 IP 首部“选项”字段的最大字节数为 40 字节,所以最多只能记录 10 个 IP。

ping 功能通过 ICMP 的回显请求和回显应答来实现,也就是说 ping 是基于 ICMP 协议实现的。

ICMP 回显请求和回显应答的报文格式如下:

  • 标识符:在实现中,一般将该字段设置为当前进程 ID。这样即使在同一台主机上同时运行了多个 ping 程序实例, ping 程序也可以识别出返回的信息属于哪个进程。
  • 序号:序号一般从 0 开始(没有强制性,从任何数字开始都可以),每发送一次新的回显请求就加 1。因为 ICMP 是在 IP 数据报内部被传输的,而 IP 协议又是不可靠、无连接的,所以 ping 程序打印出返回的每个分组的序列号,方便我们查看是否有分组丢失、失序或重复。
  • 选项:在“选项”字段中,我们一般放入发送时间戳,这样在收到回应的时候可以用来计算本次 ping 的耗时。我们经常会指定 ping 包的大小,所以也会在“选项”字段中填充一些废数据来让包达到一定大小,在下面的FillPingPacket函数就有这样的实现。

2.1.2 C++代码实现

定义 ICMP、ping 首部

networkprotocolheader.h头文件中定义了 IP 协议、ICMP 协议等协议的首部结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma pack(1)
#define __u8 unsigned char
#define __u16 unsigned short
#define __u32 unsigned long

struct icmp_common_hdr {
__u8 type;
__u8 code;
__u16 check;
/*Other content start here. */
};

struct ping_header {
icmp_common_hdr common_hdr;
__u16 id;
__u16 seq;
__u32 timestamp;
};
#pragma pack()

程序执行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE_bool(h); // 帮助
DECLARE_bool(t); // ping指定的主机直到停止
DECLARE_int(w); // 等待每次回复的超时时间(毫秒)
DECLARE_int(s); // 发送ping包超时时间(毫秒)
DECLARE_int(l); // 发送缓冲区大小
DECLARE_int(i); // TTL

DEFINE_bool(h, false, "帮助");
DEFINE_bool(t, false, "ping指定的主机直到停止");
DEFINE_int(w, 3000, "等待每次回复的超时时间(毫秒)");
DEFINE_int(s, 3000, "发送ping包超时时间(毫秒)");
DEFINE_int(l, 32, "发送缓冲区大小");
DEFINE_int(i, 128, "TTL");

ping 程序的执行参数的定义和解析由 webrtc 的"rtc_base/flags.h"支持。

完整代码

代码中的某些功能,如参数解析、断言、时间戳等基于 webrtc 的rtc_base实现,这些功能也可以很方便的自己实现。

另外,使用原始套接字需要管理员权限,如果需要绕开管理员权限,可以使用 windows 提供的IcmpSendEcho系列函数。

在发送 ping 请求的时候,我们只封装了一个 ICMP 报文,并没有自己手动添加 IP 头,封装 IP 报文。因为内核会自动添加 IP 头,如果想自己添加 IP 头,可以调用setsockopt设置IP_HDRINCL选项,告诉内核由我们自己来封装 IP 头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#include <WinSock2.h>
#include <ws2spi.h>
#include <ws2tcpip.h>
#include "rtc_base/networkprotocolheader.h"
#include "rtc_base/checks.h"
#include "rtc_base/flags.h"
#include "rtc_base/timeutils.h"

DECLARE_bool(h); // 帮助
DECLARE_bool(t); // ping指定的主机直到停止
DECLARE_int(w); // 等待每次回复的超时时间(毫秒)
DECLARE_int(s); // 发送ping包超时时间(毫秒)
DECLARE_int(l); // 发送缓冲区大小
DECLARE_int(i); // TTL

DEFINE_bool(h, false, "帮助");
DEFINE_bool(t, false, "ping指定的主机直到停止");
DEFINE_int(w, 3000, "等待每次回复的超时时间(毫秒)");
DEFINE_int(s, 3000, "发送ping包超时时间(毫秒)");
DEFINE_int(l, 32, "发送数据大小");
DEFINE_int(i, 128, "TTL");



void FillPingPacket(__u8* icmp_packet, __u16 seq, __u16 icmp_packet_size) {
RTC_DCHECK(icmp_packet);
ping_hdr* pping_hdr = reinterpret_cast<ping_hdr*>(icmp_packet);
pping_hdr->common_hdr.type = 8;
pping_hdr->common_hdr.code = 0;
pping_hdr->id = (__u16)GetCurrentProcessId();
pping_hdr->seq = seq;
__u32 now = rtc::Time32();

memcpy((icmp_packet + sizeof(ping_hdr)), &now, sizeof(__u32));

// fill some junk in the buffer.
int junk_data_size = FLAG_l - sizeof(__u32); // timestamp
int junk_offset = icmp_packet_size - junk_data_size;

if(junk_data_size > 0)
memset((icmp_packet + junk_offset), 'E', junk_data_size);

pping_hdr->common_hdr.check = 0;
pping_hdr->common_hdr.check = rtc::GetCheckSum(reinterpret_cast<__u16*>(icmp_packet), icmp_packet_size);
}

void DecodeIPPacket(__u8* ip_packet, __u16 packet_size) {
iphdr* ip_hdr = reinterpret_cast<iphdr*>(ip_packet);
__u32 now = rtc::Time32();

__u16 ip_hdr_len = ip_hdr->ihl * 4; // bytes

ping_hdr *pping_hdr = reinterpret_cast<ping_hdr*>(ip_packet + ip_hdr_len);
if (pping_hdr->common_hdr.type != 0 || pping_hdr->common_hdr.code != 0) {
printf("non-echo response, type=%d, code=%d\n", pping_hdr->common_hdr.type, pping_hdr->common_hdr.code);
return;
}

if (pping_hdr->id != (__u16)GetCurrentProcessId()) {
printf("other process ping response packet, pid=%d\n", GetCurrentProcessId());
return;
}

__u32 timestamp = 0;
memcpy(&timestamp, reinterpret_cast<__u32*>((__u8*)pping_hdr + sizeof(ping_hdr)), sizeof(__u32));

in_addr from;
from.s_addr = ip_hdr->saddr;
printf("%d bytes from %s, time < %d ms, icmp_seq = %d, TTL = %d \n",
packet_size - ip_hdr_len - sizeof(ping_hdr),
inet_ntoa(from),
now - timestamp,
pping_hdr->seq,
ip_hdr->ttl
);
}

int main(int argc, char**argv)
{
rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
if (FLAG_h) {
rtc::FlagList::Print(NULL, false);
return 1;
}

char *hostname = argv[argc - 1];
if (!hostname || strlen(hostname) == 0) {
printf("Invalid host name\n");
return 1;
}

if (FLAG_l <= 4) {
return 1;
}

WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);


sockaddr_in from;
int from_len = sizeof(sockaddr_in);

sockaddr_in dest;
memset(&dest, 0, sizeof(sockaddr_in));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr(hostname);

// resolve host name
if (dest.sin_addr.s_addr == INADDR_NONE) {
unsigned long begin_time = rtc::Time32();
struct addrinfo* result = nullptr;
struct addrinfo hints = { 0 };
hints.ai_family = AF_UNSPEC;

hints.ai_flags = AI_ADDRCONFIG;
int ret = getaddrinfo(hostname, nullptr, &hints, &result);
if (ret != 0) {
printf("Resolve host name failed, error code = %d\n", ret);
return 1;
}
unsigned long end_time = rtc::Time32();
struct addrinfo* cursor = result;
printf("------------------------------\n");
printf("Resolve [time < %d ms]: \n", end_time - begin_time);
bool flag = false;
for (; cursor; cursor = cursor->ai_next) {
sockaddr_in *paddr_in = reinterpret_cast<sockaddr_in *>(cursor->ai_addr);
printf("%s\n", inet_ntoa(paddr_in->sin_addr));

if (!flag) {
dest.sin_addr = paddr_in->sin_addr;
flag = true;
}
}
freeaddrinfo(result);
printf("-------------------------------\n");
}

printf("Ping %s [TTL %d]: \n", inet_ntoa(dest.sin_addr), FLAG_i);

// socket函数需要管理员权限
// 需要绕开管理员权限,可以使用IcmpSendEcho系列函数
//
SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (s == INVALID_SOCKET) {
printf("create socket failed, error code = %d\n", WSAGetLastError());
WSACleanup();
return 1;
}

int err = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&FLAG_s), sizeof(FLAG_s));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_SNDTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&FLAG_w), sizeof(FLAG_w));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_RCVTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

err = setsockopt(s, IPPROTO_IP, IP_TTL, reinterpret_cast<const char*>(&FLAG_i), sizeof(FLAG_i));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for IP_TTL failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

// ping request
int icmp_packet_size = sizeof(ping_hdr)
+ FLAG_l; // data

__u8 *icmp_packet = new __u8[icmp_packet_size];
RTC_DCHECK(icmp_packet);

// ping response
__u16 ip_packet_size = icmp_packet_size + 20; // 20 bytes ip header, no option.
__u8 *ip_packet = new __u8[ip_packet_size];
RTC_DCHECK(ip_packet);

if (!icmp_packet || !ip_packet) {
closesocket(s);
WSACleanup();
return 1;
}

__u16 i = 0;
while (true) {
if (i == 0xFFFF)
i = 0;
i++;

if (!FLAG_t) {
if(i > 4)
break;
}

FillPingPacket(icmp_packet, i, icmp_packet_size);

int sent = sendto(s,
reinterpret_cast<const char*>(icmp_packet),
icmp_packet_size,
0,
reinterpret_cast<const sockaddr*>(&dest),
sizeof(sockaddr));

if (sent == SOCKET_ERROR) {
int gle = WSAGetLastError();
if (gle == WSAETIMEDOUT) {
printf("request timeout\n");
continue;
}
else {
printf("ping %s failed, error code = %d\n", inet_ntoa(dest.sin_addr), gle);
break;
}
}

if (sent < FLAG_l) {
printf("warning, sent %d bytes\n", sent);
}

int bread = recvfrom(s,
reinterpret_cast<char*>(ip_packet),
ip_packet_size,
0,
reinterpret_cast<sockaddr*>(&from),
&from_len);

if (bread == SOCKET_ERROR) {
int gle = WSAGetLastError();
if (gle == WSAETIMEDOUT) {
printf("receive timeout\n");
continue;
}
else {
printf("ping %s failed, error code = %d\n", inet_ntoa(dest.sin_addr), gle);
break;
}
}

if (bread < ip_packet_size) {
printf("too few bytes from %s\n", inet_ntoa(from.sin_addr));
continue;
}

DecodeIPPacket(reinterpret_cast<__u8*>(ip_packet), ip_packet_size);

Sleep(1000);
}

delete [] icmp_packet;
delete [] ip_packet;

if (s != INVALID_SOCKET) {
closesocket(s);
}

WSACleanup();
return 0;
}

运行效果

2.2 Tracert

Tracert 是 windows 系统提供的一个工具,使用该程序可以让我们看到 IP 数据报从一台主机到另一台主机所经过的路由器。Linux 系统也提供了类似的工具,叫 traceroute,功能和 Tracert 一样。

2.2.1 Tracert 原理

在介绍 Tracert 的原理之前,需要先弄清楚 IP 首部 TTL 字段的含义,IP 报文每经过一个路由器,路由器都会将该 IP 报文首部的 TTL 字段减 1,
当路由器收到一份 IP 数据报的 TTL 是 0 或 1 时,路由器此时不会转发该数据报,而会丢弃该数据报,并且给 IP 数据报首部中的源地址发送一份 ICMP 超时报文。

IP 首部的定义见:网络协议(3)--IP协议

Tracert 利用了路由器会丢弃 TTL 为 1 或 0 的数据报且返回 ICMP 超时报文的特性,来实现侦测路由的功能。Tracert 程序先发送 TTL 值为 1 的 IP 数据报,处理这份数据报的第一个路由器将 TTL 减 1,丢弃该数据报并返回 ICMP 超时报文,这样程序就得到了第一个路由器的地址,以此方式,递增 IP 数据报 TTL 的值,直到数据报最终到达目标主机。

那么怎么判断数据报到达了最终的目标主机呢?
我们不能单纯的通过未收到路由器返回的 ICMP 差错报文的方式来判断数据报到达目的地了,因为有可能我们由于接收 ICMP 差错报文超时等原因导致我们收不到 ICMP 差错报文(这也是为什么我们后面会介绍每一个 TTL 跃点会发送 3 次或多次请求的原因)。windows 平台的 tracert 与 linux 平台的 traceroute 的实现原理稍有不同,判断数据报到达目标主机的方式也有不同。
tracert 是通过发送 ping 包,因为 windows 系统内核都实现了 ping 功能,所以如果目的主机收到了 ping 请求就会回复相应的 ping 包,tracert 就是通过这种方式来判断数据报是否到达了目标主机。而 traceroute 是通过发送 UDP 包(UDP 端口选择一个不可能使用的 UDP 端口,比如大于 30000 的端口),因为目的主机没有监听该端口,所以不会响应接收到的该 UDP 请求,因此当 UDP 包到达时,目标主机会返回“端口不可达”的错误,traceroute 就是通过该错误来判断 UDP 包到达了目的主机。

从实现方式来看,traceroute 通过 UDP 的方式来实现更加稳定可靠,因为大多数主机的防火墙会组织 ICMP 报文,而不会阻止 UDP 报文。

下图使用 wireshark 抓取的tracert 192.168.3.76命令的数据包,从图中可以看到 tracert 是通过发送 ping 包来实现的,以及每个 ping 包的 TTL 递增过程:

2.2.2 ICMP 差错报文格式

路由器在丢弃 TTL 为 0 或 1 的数据报时,会发送一个一份 ICMP 差错报文,该 ICMP 的差错报文的 type 为 11, code 为 0.

type 为 11 的报文格式如下(code 有 0 和 1 两种,格式一样):

2.2.3 实现

该示例和之前的 Ping 程序的示例有所不同,该示例设置了IP_HDRINCL选项来自己构造 IP 头部。

程序的启动参数使用 webrtc 的"rtc_base/flags.h"实现。

代码中的其他某些功能,如断言、时间戳等基于 webrtc 的rtc_base实现,这些功能也可以很方便的自己实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include <WinSock2.h>
#include <ws2spi.h>
#include <ws2tcpip.h>
#include <strsafe.h>
#include <algorithm>
#include "rtc_base/networkprotocolheader.h"
#include "rtc_base/checks.h"
#include "rtc_base/flags.h"
#include "rtc_base/timeutils.h"

DECLARE_bool(h); // 帮助
DECLARE_int(m); // 最大跃点数
DECLARE_int(w); // 等待每次回复的超时时间(毫秒)
DECLARE_int(s); // 发送ICMP包超时时间(毫秒)
DECLARE_int(n); // 每个跃点发送的请求数

DEFINE_bool(h, false, "帮助");
DEFINE_int(m, 30, "最大跃点数");
DEFINE_int(w, 3000, "等待每次回复的超时时间(毫秒)");
DEFINE_int(s, 3000, "发送ICMP包超时时间(毫秒)");
DEFINE_int(n, 3, "每个跃点发送的请求数");

const int kPingDataSize = 36;
__u32 kLocalIP = 0;

__u32 GetLocalIPv4Address() {
return inet_addr("192.168.42.26");
char hostname[MAX_PATH] = { 0 };
gethostname(hostname, MAX_PATH);
struct hostent FAR* lpHostEnt = gethostbyname(hostname);
if (lpHostEnt == NULL) {
return htonl(0x7f000001); //127.0.0.1
}

LPSTR lpAddr = lpHostEnt->h_addr_list[0];

struct in_addr addr;
memcpy(&addr, lpAddr, 4);

return addr.s_addr;
}

std::string IPv4ToString(__u32 ip) {
in_addr addr;
addr.s_addr = ip;
char *p= inet_ntoa(addr);
if (p)
return p;
return "";
}

std::string GetPrintString(const char* fmt, ...) {
char buf[100];
va_list arglist;
va_start(arglist, fmt);

StringCchVPrintfA(buf, 100, fmt, arglist);
va_end(arglist);

return buf;
}


bool print_ip(__u32* ips, int count, __u32 dest_ip) {
bool has_ip = false;
bool trace_end = false;

for (int i = 0; i < count; i++) {
if (ips[i] != 0) {
printf(" %s", IPv4ToString(ips[i]).c_str());
has_ip = true;
trace_end = (ips[i] == dest_ip);
}
}
if (!has_ip)
printf(" timeout");

printf("\n");

if (trace_end)
printf("Trace Complete\n");
return trace_end;
}


void FillRequestIPPacket(__u8* ip_packet, __u16 ip_packet_size, __u16 seq, __u8 ttl, __u32 dest_addr) {
RTC_DCHECK(ip_packet);

iphdr* p_iphdr = reinterpret_cast<iphdr*>(ip_packet);
memset(p_iphdr, 0, sizeof(iphdr));
p_iphdr->version = 4;
p_iphdr->ihl = sizeof(iphdr)/4; // no option
p_iphdr->tos = 0;
p_iphdr->frag_off = 0;
p_iphdr->id = (__u16)rtc::Time32();
p_iphdr->ttl = ttl;
p_iphdr->protocol = IPPROTO_ICMP;
p_iphdr->tot_len = ip_packet_size;
p_iphdr->daddr = dest_addr;
p_iphdr->saddr = kLocalIP;
p_iphdr->check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_iphdr), p_iphdr->ihl*4);

ping_hdr* p_ping_hdr = reinterpret_cast<ping_hdr*>(ip_packet + p_iphdr->ihl*4);
p_ping_hdr->common_hdr.type = 8;
p_ping_hdr->common_hdr.code = 0;
p_ping_hdr->id = (__u16)GetCurrentProcessId();
p_ping_hdr->seq = seq;

// fill some junk in the buffer.
if (kPingDataSize > 0)
memset((void*)((__u8*)p_ping_hdr+sizeof(ping_hdr)), 'E', kPingDataSize);

p_ping_hdr->common_hdr.check = 0;
p_ping_hdr->common_hdr.check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_ping_hdr), ip_packet_size - p_iphdr->ihl*4);
}

// return source ip address
bool DecodeIPPacket(const __u8* ip_packet, __u16 ip_packet_size, __u32 send_time, __u32* src_addr) {
const iphdr* ip_hdr = reinterpret_cast<const iphdr*>(ip_packet);
__u32 use_time = rtc::Time32() - send_time;

__u16 ip_hdr_len = ip_hdr->ihl * 4; // bytes

const icmp_common_hdr *icmp_hdr = reinterpret_cast<const icmp_common_hdr*>(ip_packet + ip_hdr_len);
if (icmp_hdr->type == 0 && icmp_hdr->code == 0) { // 回显应答
const ping_hdr *p_ping_hdr = reinterpret_cast<const ping_hdr*>(icmp_hdr);
if (p_ping_hdr->id != (__u16)GetCurrentProcessId()) {
printf("other process ping response packet, pid=%d\n", GetCurrentProcessId());
return false;
}

printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());
*src_addr = ip_hdr->saddr;
return true;
}
else if (icmp_hdr->type == 11 && icmp_hdr->code == 0) { // cause by ttl == 0
printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());

*src_addr = ip_hdr->saddr;
return true;
}
else {
printf("unexpected response, type=%d, code=%d\n", icmp_hdr->type, icmp_hdr->code);
return false;
}
}

#define SAFE_RELEASE \
if (req_ip_packet) { \
delete[] req_ip_packet; \
req_ip_packet = NULL;\
}\
if (rsp_ip_packet) { \
delete[] rsp_ip_packet; \
rsp_ip_packet = NULL;\
}\
if (s != INVALID_SOCKET) {\
closesocket(s);\
s = INVALID_SOCKET;\
}\
WSACleanup();

int main(int argc, char**argv) {
rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
if (FLAG_h) {
rtc::FlagList::Print(NULL, false);
return 1;
}

char *hostname = argv[argc - 1];
if (!hostname || strlen(hostname) == 0) {
printf("Invalid host name\n");
return 1;
}

printf("Trace %s\n", hostname);

WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);

sockaddr_in from;
int from_len = sizeof(sockaddr_in);

kLocalIP = GetLocalIPv4Address();

sockaddr_in dest;
memset(&dest, 0, sizeof(sockaddr_in));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr(hostname);

// resolve host name
if (dest.sin_addr.s_addr == INADDR_NONE) {
unsigned long begin_time = rtc::Time32();
struct addrinfo* result = nullptr;
struct addrinfo hints = { 0 };
hints.ai_family = AF_UNSPEC;

hints.ai_flags = AI_ADDRCONFIG;
int ret = getaddrinfo(hostname, nullptr, &hints, &result);
if (ret != 0) {
printf("Resolve host name failed, error code = %d\n", ret);
return 1;
}
unsigned long end_time = rtc::Time32();
struct addrinfo* cursor = result;
printf("------------------------------\n");
printf("Resolve [time < %d ms]: \n", end_time - begin_time);
bool flag = false;
for (; cursor; cursor = cursor->ai_next) {
sockaddr_in *paddr_in = reinterpret_cast<sockaddr_in *>(cursor->ai_addr);
printf("%s\n", inet_ntoa(paddr_in->sin_addr));

if (!flag) {
dest.sin_addr = paddr_in->sin_addr;
flag = true;
}
}
freeaddrinfo(result);
printf("-------------------------------\n");
}

printf("Tracing %s [%d max hops]: \n", inet_ntoa(dest.sin_addr), FLAG_m);

SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (s == INVALID_SOCKET) {
printf("create socket failed, error code = %d\n", WSAGetLastError());
WSACleanup();
return 1;
}

int err = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&FLAG_s), sizeof(FLAG_s));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_SNDTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&FLAG_w), sizeof(FLAG_w));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_RCVTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

int opt = 1;
err = setsockopt(s, IPPROTO_IP, IP_HDRINCL, reinterpret_cast<const char*>(&opt), sizeof(opt));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for IP_HDRINCL failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}

// ip packet for ping request.
__u16 req_ip_packet_size = sizeof(iphdr) + sizeof(ping_hdr) + kPingDataSize;
__u8 *req_ip_packet = new __u8[req_ip_packet_size];
memset(req_ip_packet, 0, req_ip_packet_size);

// ip packet for icmp response or ping echo.
__u16 rsp_ip_packet_size = // ICMP差错报文的大小
sizeof(iphdr)
+ sizeof(icmp_common_hdr) // ICMP(type=11,code=0或1)差错报文
+ 4 // unused
+ sizeof(iphdr) + 8;

//取ping包大小和ICMP差错报文大小的最大值,保证无论返回哪种报文缓冲区都够用,
//也可以直接分配一个足够大的缓冲区,如1024
//
rsp_ip_packet_size = std::max(rsp_ip_packet_size, req_ip_packet_size);

__u8 *rsp_ip_packet = new __u8[rsp_ip_packet_size];
memset(rsp_ip_packet, 0, rsp_ip_packet_size);
RTC_DCHECK(rsp_ip_packet);

int ttl = 1;
int seq = 0;
__u32 *ips = new __u32[FLAG_n];

for(int hop = 1; hop <= FLAG_m; hop++) {
printf(" %-4d", hop);

for (int i = 0; i < FLAG_n; i++) {
ips[i] = 0;
seq++;
FillRequestIPPacket(req_ip_packet, req_ip_packet_size, seq, ttl, dest.sin_addr.s_addr);

__u32 send_time = rtc::Time32();
int sent = sendto(s,
reinterpret_cast<const char*>(req_ip_packet),
req_ip_packet_size,
0,
reinterpret_cast<const sockaddr*>(&dest),
sizeof(sockaddr));

if (sent == SOCKET_ERROR) {
printf("%-10s", "*");

if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
continue;
}


int bread = recvfrom(s,
reinterpret_cast<char*>(rsp_ip_packet),
rsp_ip_packet_size,
0,
reinterpret_cast<sockaddr*>(&from),
&from_len);

if (bread == SOCKET_ERROR) {
int gle = WSAGetLastError();
printf("%-10s", "*");

if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
continue;
}

__u32 dest_ip = 0;
DecodeIPPacket(reinterpret_cast<const __u8*>(rsp_ip_packet), rsp_ip_packet_size, send_time, &dest_ip);
ips[i] = dest_ip;

if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
}

ttl++;
}

SAFE_RELEASE;
return 0;
}

2.2.4 运行效果:

文章图片带有“CSDN”水印的说明:
由于该文章和图片最初发表在我的CSDN 博客中,因此图片被 CSDN 自动添加了水印。