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