1/* See LICENSE file for copyright and license details. */
2
3#include "arg.h"
4#include "util.h"
5
6#include <arpa/inet.h>
7#include <errno.h>
8#include <netdb.h>
9#include <netinet/in.h>
10#include <netinet/ip.h>
11#include <netinet/ip_icmp.h>
12#include <poll.h>
13#include <signal.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <sys/socket.h>
18#include <sys/time.h>
19#include <sys/types.h>
20#include <unistd.h>
21
22static int keep_running = 1;
23
24static void
25usage(void)
26{
27 eprintf(
28 "usage: %s [-c count] [-i interval] [-s size] [-t ttl] [-w "
29 "deadline] [-q] host\n",
30 argv0
31 );
32}
33
34static void
35sigint_handler(int sig)
36{
37 (void)sig;
38 keep_running = 0;
39}
40
41static unsigned short
42checksum(unsigned short *addr, int len)
43{
44 unsigned long sum = 0;
45
46 while (len > 1) {
47 sum += *addr++;
48 len -= 2;
49 }
50 if (len > 0)
51 sum += *(unsigned char *)addr;
52 while (sum >> 16)
53 sum = (sum & 0xffff) + (sum >> 16);
54 return ~sum;
55}
56
57static void
58send_ping(int sock, struct sockaddr_in *dst, int seq, int size)
59{
60 struct icmphdr *icmp;
61 char *packet;
62 struct timeval tv;
63 int packlen = sizeof(*icmp) + size;
64
65 packet = emalloc(packlen);
66 memset(packet, 0, packlen);
67
68 icmp = (struct icmphdr *)packet;
69 icmp->type = ICMP_ECHO;
70 icmp->code = 0;
71 icmp->un.echo.id = htons(getpid() & 0xffff);
72 icmp->un.echo.sequence = htons(seq);
73
74 /* store timestamp in payload if size is large enough */
75 if (size >= (int)sizeof(struct timeval)) {
76 gettimeofday(&tv, NULL);
77 memcpy(packet + sizeof(*icmp), &tv, sizeof(tv));
78 }
79
80 icmp->checksum = checksum((unsigned short *)packet, packlen);
81
82 if (sendto(sock, packet, packlen, 0, (struct sockaddr *)dst, sizeof(*dst)) < 0)
83 eprintf("sendto:");
84
85 free(packet);
86}
87
88static double
89get_time_ms(void)
90{
91 struct timeval tv;
92 gettimeofday(&tv, NULL);
93 return (double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0;
94}
95
96static void
97print_stats(
98 const char *host, int sent, int received, double min_rtt, double max_rtt, double sum_rtt
99)
100{
101 printf("\n--- %s ping statistics ---\n", host);
102 printf(
103 "%d packets transmitted, %d received, %d%% packet loss\n",
104 sent,
105 received,
106 sent > 0 ? (sent - received) * 100 / sent : 0
107 );
108 if (received > 0) {
109 printf("rtt min/avg/max = %.3f/%.3f/%.3f ms\n", min_rtt, sum_rtt / received, max_rtt);
110 }
111}
112
113// ?man ping: send icmp echo requests
114// ?man arguments: host
115// ?man send icmp echo requests to verify network connectivity
116int
117main(int argc, char *argv[])
118{
119 struct addrinfo hints, *res;
120 struct sockaddr_in dst;
121 struct sockaddr_in from;
122 struct pollfd pfd;
123 struct timeval sent_tv;
124 struct icmphdr *icmp;
125 char *cflag = NULL;
126 char *iflag = NULL;
127 char *sflag = NULL;
128 char *tflag = NULL;
129 char *wflag = NULL;
130 char *host;
131 char *rxbuf;
132 int qflag = 0;
133 int count = 0;
134 int size = 56;
135 int ttl = 0;
136 int deadline = 0;
137 int sock = -1;
138 int is_raw = 1;
139 int seq = 1;
140 int sent = 0;
141 int received = 0;
142 int r, optval, p_res, hlen;
143 double interval = 1.0;
144 double min_rtt = 999999.0;
145 double max_rtt = 0.0;
146 double sum_rtt = 0.0;
147 double start_time, deadline_ms, next_send_ms, now, timeout_ms, time_to_deadline, rtt, sent_ms;
148 ssize_t n;
149 socklen_t fromlen;
150
151 ARGBEGIN
152 {
153 // ?man -c:str: print count or perform stdout action
154 case 'c':
155 cflag = EARGF(usage());
156 break;
157 // ?man -i:str: interactive mode or prompt for confirmation
158 case 'i':
159 iflag = EARGF(usage());
160 break;
161 // ?man -s:str: silent mode or print summary
162 case 's':
163 sflag = EARGF(usage());
164 break;
165 // ?man -t:str: sort or specify timestamp
166 case 't':
167 tflag = EARGF(usage());
168 break;
169 // ?man -w:str: wait for completion
170 case 'w':
171 wflag = EARGF(usage());
172 break;
173 // ?man -q: quiet mode; suppress output
174 case 'q':
175 qflag = 1;
176 break;
177 default:
178 usage();
179 }
180 ARGEND
181
182 if (argc < 1)
183 usage();
184
185 host = argv[0];
186
187 if (cflag)
188 count = estrtonum(cflag, 1, 1000000);
189 if (iflag)
190 interval = estrtod(iflag);
191 if (sflag)
192 size = estrtonum(sflag, 0, 65507);
193 if (tflag)
194 ttl = estrtonum(tflag, 1, 255);
195 if (wflag)
196 deadline = estrtonum(wflag, 1, 1000000);
197
198 memset(&hints, 0, sizeof(hints));
199 hints.ai_family = AF_INET;
200 hints.ai_socktype = SOCK_RAW;
201
202 r = getaddrinfo(host, NULL, &hints, &res);
203 if (r != 0)
204 eprintf("getaddrinfo %s: %s\n", host, gai_strerror(r));
205
206 memcpy(&dst, res->ai_addr, sizeof(dst));
207 freeaddrinfo(res);
208
209 /* try opening raw icmp socket first, fallback to dgram */
210 sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
211 if (sock < 0) {
212 sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
213 if (sock < 0)
214 eprintf("socket:");
215 is_raw = 0;
216 }
217
218 if (ttl > 0) {
219 if (setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0)
220 eprintf("setsockopt IP_TTL:");
221 }
222
223 /* request that recvfrom returns the ttl value */
224 optval = 1;
225 if (is_raw) {
226 setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &optval, sizeof(optval));
227 }
228
229 if (!qflag) {
230 printf("PING %s (%s) %d bytes of data.\n", host, inet_ntoa(dst.sin_addr), size);
231 }
232
233 signal(SIGINT, sigint_handler);
234
235 pfd.fd = sock;
236 pfd.events = POLLIN;
237
238 start_time = get_time_ms();
239 deadline_ms = deadline > 0 ? start_time + deadline * 1000.0 : 0.0;
240 next_send_ms = start_time;
241
242 rxbuf = emalloc(size + 1024);
243
244 while (keep_running) {
245 now = get_time_ms();
246
247 if (deadline > 0 && now >= deadline_ms)
248 break;
249
250 if (count > 0 && sent >= count && received >= sent)
251 break;
252
253 if (now >= next_send_ms && (count == 0 || sent < count)) {
254 send_ping(sock, &dst, seq++, size);
255 sent++;
256 next_send_ms = now + interval * 1000.0;
257 }
258
259 timeout_ms = next_send_ms - now;
260 if (deadline > 0) {
261 time_to_deadline = deadline_ms - now;
262 if (time_to_deadline < timeout_ms)
263 timeout_ms = time_to_deadline;
264 }
265 if (timeout_ms < 0)
266 timeout_ms = 0;
267
268 if (count > 0 && sent >= count)
269 timeout_ms = 2000.0;
270
271 p_res = poll(&pfd, 1, (int)timeout_ms);
272 if (p_res < 0) {
273 if (errno == EINTR)
274 continue;
275 eprintf("poll:\n");
276 }
277
278 if (p_res > 0 && (pfd.revents & POLLIN)) {
279 fromlen = sizeof(from);
280 n = recvfrom(sock, rxbuf, size + 1024, 0, (struct sockaddr *)&from, &fromlen);
281 if (n < 0) {
282 if (errno == EINTR || errno == EAGAIN)
283 continue;
284 eprintf("recvfrom:\n");
285 }
286
287 hlen = 0;
288 if (is_raw) {
289 struct ip *ip = (struct ip *)rxbuf;
290 hlen = ip->ip_hl << 2;
291 if (n < hlen + (int)sizeof(*icmp))
292 continue;
293 }
294 icmp = (struct icmphdr *)(rxbuf + hlen);
295
296 if (icmp->type == ICMP_ECHOREPLY
297 && (!is_raw || ntohs(icmp->un.echo.id) == (getpid() & 0xffff))) {
298 received++;
299 rtt = get_time_ms();
300 if (n - hlen - (int)sizeof(*icmp) >= (int)sizeof(struct timeval)) {
301 memcpy(&sent_tv, rxbuf + hlen + sizeof(*icmp), sizeof(sent_tv));
302 sent_ms = (double)sent_tv.tv_sec * 1000.0 + (double)sent_tv.tv_usec / 1000.0;
303 rtt -= sent_ms;
304 if (rtt < min_rtt)
305 min_rtt = rtt;
306 if (rtt > max_rtt)
307 max_rtt = rtt;
308 sum_rtt += rtt;
309 if (!qflag) {
310 printf(
311 "%d bytes from %s: "
312 "icmp_seq=%d ttl=%d "
313 "time=%.3f ms\n",
314 (int)n,
315 inet_ntoa(from.sin_addr),
316 ntohs(icmp->un.echo.sequence),
317 is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64,
318 rtt
319 );
320 }
321 } else {
322 if (!qflag) {
323 printf(
324 "%d bytes from %s: "
325 "icmp_seq=%d ttl=%d\n",
326 (int)n,
327 inet_ntoa(from.sin_addr),
328 ntohs(icmp->un.echo.sequence),
329 is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64
330 );
331 }
332 }
333 }
334 }
335 }
336
337 free(rxbuf);
338 close(sock);
339
340 print_stats(host, sent, received, min_rtt, max_rtt, sum_rtt);
341
342 return received > 0 ? 0 : 1;
343}