1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <arpa/inet.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <netdb.h>
10 #include <netinet/in.h>
11 #include <netinet/ip_icmp.h>
12 #include <poll.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17
18 #include <zircon/compiler.h>
19 #include <zircon/syscalls.h>
20
21 const int MAX_PAYLOAD_SIZE_BYTES = 1400;
22
23 typedef struct {
24 icmphdr hdr;
25 uint8_t payload[MAX_PAYLOAD_SIZE_BYTES];
26 } __PACKED packet_t;
27
28 struct Options {
29 long interval_msec = 1000;
30 long payload_size_bytes = 0;
31 long count = 3;
32 long timeout_msec = 1000;
33 const char* host = nullptr;
34 long min_payload_size_bytes = 0;
35
OptionsOptions36 explicit Options(long min) {
37 payload_size_bytes = min;
38 min_payload_size_bytes = min;
39 }
40
PrintOptions41 void Print() const {
42 printf("Count: %ld, ", count);
43 printf("Payload size: %ld bytes, ", payload_size_bytes);
44 printf("Interval: %ld ms, ", interval_msec);
45 printf("Timeout: %ld ms, ", timeout_msec);
46 if (host != nullptr) {
47 printf("Destination: %s\n", host);
48 }
49 }
50
ValidateOptions51 bool Validate() const {
52 if (interval_msec <= 0) {
53 fprintf(stderr, "interval must be positive: %ld\n", interval_msec);
54 return false;
55 }
56
57 if (payload_size_bytes >= MAX_PAYLOAD_SIZE_BYTES) {
58 fprintf(stderr, "payload size must be smaller than: %d\n", MAX_PAYLOAD_SIZE_BYTES);
59 return false;
60 }
61
62 if (payload_size_bytes < min_payload_size_bytes) {
63 fprintf(stderr, "payload size must be more than: %ld\n", min_payload_size_bytes);
64 return false;
65 }
66
67 if (count <= 0) {
68 fprintf(stderr, "count must be positive: %ld\n", count);
69 return false;
70 }
71
72 if (timeout_msec <= 0) {
73 fprintf(stderr, "timeout must be positive: %ld\n", timeout_msec);
74 return false;
75 }
76
77 if (host == nullptr) {
78 fprintf(stderr, "destination must be provided\n");
79 return false;
80 }
81 return true;
82 }
83
UsageOptions84 int Usage() const {
85 fprintf(stderr, "\n\tUsage: ping [ <option>* ] destination\n");
86 fprintf(stderr, "\n\tSend ICMP ECHO_REQUEST to a destination. This destination\n");
87 fprintf(stderr, "\tmay be a hostname (google.com) or an IP address (8.8.8.8).\n\n");
88 fprintf(stderr, "\t-c count: Only send count packets (default = 3)\n");
89 fprintf(stderr, "\t-i interval(ms): Time interval between pings (default = 1000)\n");
90 fprintf(stderr, "\t-t timeout(ms): Timeout waiting for ping response (default = 1000)\n");
91 fprintf(stderr, "\t-s size(bytes): Number of payload bytes (default = %ld, max 1400)\n",
92 payload_size_bytes);
93 fprintf(stderr, "\t-h: View this help message\n\n");
94 return -1;
95 }
96
ParseCommandLineOptions97 int ParseCommandLine(int argc, char** argv) {
98 int opt;
99 while ((opt = getopt(argc, argv, "s:c:i:t:h")) != -1) {
100 char* endptr = nullptr;
101 switch (opt) {
102 case 'h':
103 return Usage();
104 case 'i':
105 interval_msec = strtol(optarg, &endptr, 10);
106 if (*endptr != '\0') {
107 fprintf(stderr, "-i must be followed by a non-negative integer\n");
108 return Usage();
109 }
110 break;
111 case 's':
112 payload_size_bytes = strtol(optarg, &endptr, 10);
113 if (*endptr != '\0') {
114 fprintf(stderr, "-s must be followed by a non-negative integer\n");
115 return Usage();
116 }
117 break;
118 case 'c':
119 count = strtol(optarg, &endptr, 10);
120 if (*endptr != '\0') {
121 fprintf(stderr, "-c must be followed by a non-negative integer\n");
122 return Usage();
123 }
124 break;
125 case 't':
126 timeout_msec = strtol(optarg, &endptr, 10);
127 if (*endptr != '\0') {
128 fprintf(stderr, "-t must be followed by a non-negative integer\n");
129 return Usage();
130 }
131 break;
132 default:
133 return Usage();
134 }
135 }
136 if (optind >= argc) {
137 fprintf(stderr, "missing destination\n");
138 return Usage();
139 }
140 host = argv[optind];
141 return 0;
142 }
143 };
144
145 struct PingStatistics {
146 uint64_t min_rtt_msec = UINT64_MAX;
147 uint64_t max_rtt_msec = 0;
148 uint64_t sum_rtt_msec = 0;
149 uint16_t num_sent = 0;
150 uint16_t num_lost = 0;
151
UpdatePingStatistics152 void Update(uint64_t rtt_msec) {
153 if (rtt_msec < min_rtt_msec)
154 min_rtt_msec = rtt_msec;
155 if (rtt_msec > max_rtt_msec)
156 max_rtt_msec = rtt_msec;
157 sum_rtt_msec += rtt_msec;
158 num_sent++;
159 }
160
PrintPingStatistics161 void Print() const {
162 if (num_sent == 0)
163 return;
164 printf("Min RTT: %" PRIu64 " us, Max RTT: %" PRIu64 " us, Avg RTT: %" PRIu64 " us\n",
165 min_rtt_msec, max_rtt_msec, (sum_rtt_msec / num_sent));
166 }
167 };
168
ValidateReceivedPacket(const packet_t & sent_packet,size_t sent_packet_size,const packet_t & received_packet,size_t received_packet_size,const Options & options)169 bool ValidateReceivedPacket(const packet_t& sent_packet, size_t sent_packet_size,
170 const packet_t& received_packet, size_t received_packet_size,
171 const Options& options) {
172 if (received_packet_size != sent_packet_size) {
173 fprintf(stderr, "Incorrect Packet size of received packet: %zu expected %zu\n",
174 received_packet_size, sent_packet_size);
175 return false;
176 }
177 if (received_packet.hdr.type != ICMP_ECHOREPLY) {
178 fprintf(stderr, "Incorrect Header type in received packet: %d expected: %d\n",
179 received_packet.hdr.type, ICMP_ECHOREPLY);
180 return false;
181 }
182 if (received_packet.hdr.code != 0) {
183 fprintf(stderr, "Incorrect Header code in received packet: %d expected: 0\n",
184 received_packet.hdr.code);
185 return false;
186 }
187 if (received_packet.hdr.un.echo.sequence != sent_packet.hdr.un.echo.sequence) {
188 fprintf(stderr, "Incorrect Header sequence in received packet: %d expected: %d\n",
189 received_packet.hdr.un.echo.sequence, sent_packet.hdr.un.echo.sequence);
190 return false;
191 }
192 if (memcmp(received_packet.payload, sent_packet.payload, options.payload_size_bytes) != 0) {
193 fprintf(stderr, "Incorrect Payload content in received packet\n");
194 return false;
195 }
196 return true;
197 }
198
main(int argc,char ** argv)199 int main(int argc, char** argv) {
200
201 constexpr char ping_message[] = "This is an echo message!";
202 long message_size = static_cast<long>(strlen(ping_message) + 1);
203 Options options(message_size);
204 PingStatistics stats;
205
206 if (options.ParseCommandLine(argc, argv) != 0) {
207 return -1;
208 }
209
210 if (!options.Validate()) {
211 return options.Usage();
212 }
213
214 options.Print();
215
216 int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
217 if (s < 0) {
218 fprintf(stderr, "Could not acquire ICMP socket: %d\n", errno);
219 return -1;
220 }
221
222 struct addrinfo hints;
223 memset(&hints, 0, sizeof(hints));
224 hints.ai_family = AF_UNSPEC;
225 hints.ai_socktype = SOCK_DGRAM;
226 hints.ai_flags = 0;
227 hints.ai_protocol = IPPROTO_ICMP;
228 struct addrinfo* info;
229 if (getaddrinfo(options.host, NULL, &hints, &info)) {
230 fprintf(stderr, "ping: unknown host %s\n", options.host);
231 return -1;
232 }
233
234 struct sockaddr* saddr = info->ai_addr;
235 char buf[256];
236 if (saddr->sa_family == AF_INET) {
237 struct sockaddr_in* iaddr = reinterpret_cast<struct sockaddr_in*>(saddr);
238 inet_ntop(saddr->sa_family, &iaddr->sin_addr, buf, sizeof(buf));
239 } else {
240 struct sockaddr_in6* iaddr = reinterpret_cast<struct sockaddr_in6*>(saddr);
241 inet_ntop(saddr->sa_family, &iaddr->sin6_addr, buf, sizeof(buf));
242 }
243
244 printf("PING %s (%s)\n", options.host, buf);
245
246 uint16_t sequence = 1;
247 packet_t packet, received_packet;
248 ssize_t r = 0;
249 ssize_t sent_packet_size = 0;
250 const zx_ticks_t ticks_per_usec = zx_ticks_per_second() / 1000000;
251
252 while (options.count-- > 0) {
253 memset(&packet, 0, sizeof(packet));
254 packet.hdr.type = ICMP_ECHO;
255 packet.hdr.code = 0;
256 packet.hdr.un.echo.id = 0;
257 packet.hdr.un.echo.sequence = htons(sequence++);
258 strcpy(reinterpret_cast<char*>(packet.payload), ping_message);
259 // Netstack will overwrite the checksum
260 zx_ticks_t before = zx_ticks_get();
261 sent_packet_size = sizeof(packet.hdr) + options.payload_size_bytes;
262 r = sendto(s, &packet, sent_packet_size, 0, saddr, sizeof(*saddr));
263 if (r < 0) {
264 fprintf(stderr, "ping: Could not send packet\n");
265 return -1;
266 }
267
268 struct pollfd fd;
269 fd.fd = s;
270 fd.events = POLLIN;
271 switch (poll(&fd, 1, static_cast<int>(options.timeout_msec))) {
272 case 1:
273 if (fd.revents & POLLIN) {
274 r = recvfrom(s, &received_packet, sizeof(received_packet), 0, NULL, NULL);
275 if (!ValidateReceivedPacket(packet, sent_packet_size, received_packet, r, options)) {
276 fprintf(stderr, "ping: Received packet didn't match sent packet: %d\n",
277 packet.hdr.un.echo.sequence);
278 }
279 break;
280 } else {
281 fprintf(stderr, "ping: Spurious wakeup from poll\n");
282 r = -1;
283 break;
284 }
285 case 0:
286 fprintf(stderr, "ping: Timeout after %d ms\n", static_cast<int>(options.timeout_msec));
287 __FALLTHROUGH;
288 default:
289 r = -1;
290 }
291
292 if (r < 0) {
293 fprintf(stderr, "ping: Could not read result of ping\n");
294 return -1;
295 }
296 zx_ticks_t after = zx_ticks_get();
297 int seq = ntohs(packet.hdr.un.echo.sequence);
298 uint64_t usec = (after - before) / ticks_per_usec;
299 stats.Update(usec);
300 printf("%" PRIu64 " bytes: icmp_seq=%d RTT=%" PRIu64 " us\n", r, seq, usec);
301 if (options.count > 0) {
302 usleep(static_cast<unsigned int>(options.interval_msec * 1000));
303 }
304 }
305 freeaddrinfo(info);
306 stats.Print();
307 close(s);
308 return 0;
309 }
310