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