1 /* ping.c - check network connectivity
2  *
3  * Copyright 2014 Rob Landley <rob@landley.net>
4  *
5  * Not in SUSv4.
6  *
7  * Note: ping_group_range should never have existed. To disable it, do:
8  *   echo 0 999999999 > /proc/sys/net/ipv4/ping_group_range
9  * (Android does this by default in its init script.)
10  *
11  * Yes, I wimped out and capped -s at sizeof(toybuf), waiting for a complaint...
12 
13 // -s > 4064 = sizeof(toybuf)-sizeof(struct icmphdr)-CMSG_SPACE(sizeof(uint8_t)), then kernel adds 20 bytes
14 USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN))
15 USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN))
16 
17 config PING
18   bool "ping"
19   default y
20   help
21     usage: ping [OPTIONS] HOST
22 
23     Check network connectivity by sending packets to a host and reporting
24     its response.
25 
26     Send ICMP ECHO_REQUEST packets to ipv4 or ipv6 addresses and prints each
27     echo it receives back, with round trip time. Returns true if host alive.
28 
29     Options:
30     -4, -6		Force IPv4 or IPv6
31     -c CNT		Send CNT many packets (default 3, 0 = infinite)
32     -f		Flood (print . and \b to show drops, default -c 15 -i 0.2)
33     -i TIME		Interval between packets (default 1, need root for < .2)
34     -I IFACE/IP	Source interface or address
35     -m MARK		Tag outgoing packets using SO_MARK
36     -q		Quiet (stops after one returns true if host is alive)
37     -s SIZE		Data SIZE in bytes (default 56)
38     -t TTL		Set Time To Live (number of hops)
39     -W SEC		Seconds to wait for response after last -c packet (default 3)
40     -w SEC		Exit after this many seconds
41 */
42 
43 #define FOR_ping
44 #include "toys.h"
45 
46 #include <ifaddrs.h>
47 #include <netinet/ip_icmp.h>
48 
GLOBALS(char * I;long w,W,i,s,c,t,m;struct sockaddr * sa;int sock;unsigned long sent,recv,fugit,min,max;)49 GLOBALS(
50   char *I;
51   long w, W, i, s, c, t, m;
52 
53   struct sockaddr *sa;
54   int sock;
55   unsigned long sent, recv, fugit, min, max;
56 )
57 
58 // Print a summary. Called as a single handler or at exit.
59 static void summary(int sig)
60 {
61   if (!FLAG(q) && TT.sent && TT.sa) {
62     printf("\n--- %s ping statistics ---\n", ntop(TT.sa));
63     printf("%lu packets transmitted, %lu received, %ld%% packet loss\n",
64       TT.sent, TT.recv, ((TT.sent-TT.recv)*100)/(TT.sent?TT.sent:1));
65     if (TT.recv)
66       printf("round-trip min/avg/max = %lu/%lu/%lu ms\n",
67         TT.min, TT.fugit/TT.recv, TT.max);
68   }
69   TT.sa = 0;
70 }
71 
72 // assumes aligned and can read even number of bytes
pingchksum(unsigned short * data,int len)73 static unsigned short pingchksum(unsigned short *data, int len)
74 {
75   unsigned short u = 0, d;
76 
77   // circular carry is endian independent: bits from high byte go to low byte
78   while (len>0) {
79     d = *data++;
80     if (len == 1) d &= 255<<IS_BIG_ENDIAN;
81     if (d >= (u += d)) u++;
82     len -= 2;
83   }
84 
85   return u;
86 }
87 
xrecvmsgwait(int fd,struct msghdr * msg,int flag,union socksaddr * sa,int timeout)88 static int xrecvmsgwait(int fd, struct msghdr *msg, int flag,
89   union socksaddr *sa, int timeout)
90 {
91   socklen_t sl = sizeof(*sa);
92   int len;
93 
94   if (timeout >= 0) {
95     struct pollfd pfd;
96 
97     pfd.fd = fd;
98     pfd.events = POLLIN;
99     if (!xpoll(&pfd, 1, timeout)) return 0;
100   }
101 
102   msg->msg_name = (void *)sa;
103   msg->msg_namelen = sl;
104   len = recvmsg(fd, msg, flag);
105   if (len<0) perror_exit("recvmsg");
106 
107   return len;
108 }
109 
ping_main(void)110 void ping_main(void)
111 {
112   struct addrinfo *ai, *ai2;
113   struct ifaddrs *ifa, *ifa2 = 0;
114   struct icmphdr *ih = (void *)toybuf;
115   struct msghdr msg;
116   struct cmsghdr *cmsg;
117   struct iovec iov;
118   union socksaddr srcaddr, srcaddr2;
119   struct sockaddr *sa = (void *)&srcaddr;
120   int family = 0, ttl = 0, len;
121   long long tnext, tW, tnow, tw;
122   unsigned short seq = 0, pkttime;
123 
124   // Set nonstatic default values
125   if (!FLAG(i)) TT.i = FLAG(f) ? 200 : 1000;
126   else if (TT.i<200 && getuid()) error_exit("need root for -i <200");
127   if (!FLAG(s)) TT.s = 56; // 64-PHDR_LEN
128   if (FLAG(f) && !FLAG(c)) TT.c = 15;
129 
130   // ipv4 or ipv6? (0 = autodetect if -I or arg have only one address type.)
131   if (FLAG(6) || strchr(toys.which->name, '6')) family = AF_INET6;
132   else if (FLAG(4)) family = AF_INET;
133   else family = 0;
134 
135   // If -I srcaddr look it up. Allow numeric address of correct type.
136   memset(&srcaddr, 0, sizeof(srcaddr));
137   if (TT.I) {
138     if (!FLAG(6) && inet_pton(AF_INET, TT.I, (void *)&srcaddr.in.sin_addr))
139       family = AF_INET;
140     else if (!FLAG(4) && inet_pton(AF_INET6, TT.I, (void *)&srcaddr.in6.sin6_addr))
141       family = AF_INET6;
142     else if (getifaddrs(&ifa2)) perror_exit("getifaddrs");
143   }
144 
145   // Look up HOST address, filtering for correct type and interface.
146   // If -I but no -46 then find compatible type between -I and HOST
147   ai2 = xgetaddrinfo(*toys.optargs, 0, family, 0, 0, 0);
148   for (ai = ai2; ai; ai = ai->ai_next) {
149 
150     // correct type?
151     if (family && family!=ai->ai_family) continue;
152     if (ai->ai_family!=AF_INET && ai->ai_family!=AF_INET6) continue;
153 
154     // correct interface?
155     if (!TT.I || !ifa2) break;
156     for (ifa = ifa2; ifa; ifa = ifa->ifa_next) {
157       if (!ifa->ifa_addr || ifa->ifa_addr->sa_family!=ai->ai_family
158           || strcmp(ifa->ifa_name, TT.I)) continue;
159       sa = (void *)ifa->ifa_addr;
160 
161       break;
162     }
163     if (ifa) break;
164   }
165 
166   if (!ai)
167     error_exit("no v%d addr for -I %s", 4+2*(family==AF_INET6), TT.I);
168   TT.sa = ai->ai_addr;
169 
170   // Open DGRAM socket
171   sa->sa_family = ai->ai_family;
172   TT.sock = socket(ai->ai_family, SOCK_DGRAM,
173     len = (ai->ai_family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6);
174   if (TT.sock == -1) {
175     perror_msg("socket SOCK_DGRAM %x", len);
176     if (errno == EACCES) {
177       fprintf(stderr, "Kernel bug workaround:\n"
178         "echo 0 99999999 | sudo tee /proc/sys/net/ipv4/ping_group_range\n");
179     }
180     xexit();
181   }
182   if (TT.I) xbind(TT.sock, sa, sizeof(srcaddr));
183 
184   len = 1;
185   xsetsockopt(TT.sock, SOL_IP, IP_RECVTTL, &len, sizeof(len));
186 
187   if (FLAG(m)) {
188     len = TT.m;
189     xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &len, sizeof(len));
190   }
191 
192   if (TT.t) {
193     len = TT.t;
194     if (ai->ai_family == AF_INET)
195       xsetsockopt(TT.sock, IPPROTO_IP, IP_TTL, &len, 4);
196     else xsetsockopt(TT.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &len, sizeof(len));
197   }
198 
199   if (!FLAG(q)) {
200     printf("Ping %s (%s)", *toys.optargs, ntop(TT.sa));
201     if (TT.I) {
202       *toybuf = 0;
203       printf(" from %s (%s)", TT.I, ntop(sa));
204     }
205     // 20 byte TCP header, 8 byte ICMP header, plus data payload
206     printf(": %ld(%ld) bytes.\n", TT.s, TT.s+28);
207   }
208 
209   TT.min = ULONG_MAX;
210   toys.exitval = 1;
211 
212   tW = tw = 0;
213   tnext = millitime();
214   if (TT.w) tw = TT.w*1000+tnext;
215 
216   memset(&msg, 0, sizeof(msg));
217   // left enought space to store ttl value
218   len = CMSG_SPACE(sizeof(uint8_t));
219   iov.iov_base = (void *)toybuf;
220   iov.iov_len = sizeof(toybuf) - len;
221   msg.msg_iov = &iov;
222   msg.msg_iovlen = 1;
223   msg.msg_control = &toybuf[iov.iov_len];
224   msg.msg_controllen = len;
225 
226   sigatexit(summary);
227 
228   // Send/receive packets
229   for (;;) {
230     int waitms = INT_MAX;
231 
232     // Exit due to timeout? (TODO: timeout is after last packet, waiting if
233     // any packets ever dropped. Not timeout since packet was dropped.)
234     tnow = millitime();
235     if (tW) {
236       if (0>=(waitms = tW-tnow) || !(TT.sent-TT.recv)) break;
237       waitms = tW-tnow;
238     }
239     if (tw) {
240       if (tnow>tw) break;
241       else if (waitms>tw-tnow) waitms = tw-tnow;
242     }
243 
244     // Time to send the next packet?
245     if (!tW && tnext-tnow <= 0) {
246       tnext += TT.i;
247 
248       memset(ih, 0, sizeof(*ih));
249       ih->type = (ai->ai_family == AF_INET) ? 8 : 128;
250       ih->un.echo.id = getpid();
251       ih->un.echo.sequence = ++seq;
252       if (TT.s >= 4) *(unsigned *)(ih+1) = tnow;
253 
254       ih->checksum = pingchksum((void *)toybuf, TT.s+sizeof(*ih));
255       xsendto(TT.sock, toybuf, TT.s+sizeof(*ih), TT.sa);
256       TT.sent++;
257       if (FLAG(f) && !FLAG(q)) xputc('.');
258 
259       // last packet?
260       if (TT.c) if (!--TT.c) {
261         tW = tnow + TT.W*1000;
262         waitms = 1; // check for immediate return even when W=0
263       }
264     }
265 
266     // This is down here so it's against new period if we just sent a packet
267     if (!tW && waitms>tnext-tnow) waitms = tnext-tnow;
268 
269     // wait for next packet or timeout
270 
271     if (waitms<0) waitms = 0;
272     if (!(len = xrecvmsgwait(TT.sock, &msg, 0, &srcaddr2, waitms)))
273       continue;
274 
275     TT.recv++;
276     TT.fugit += (pkttime = millitime()-*(unsigned *)(ih+1));
277     if (pkttime < TT.min) TT.min = pkttime;
278     if (pkttime > TT.max) TT.max = pkttime;
279 
280     // reply id == 0 for ipv4, 129 for ipv6
281 
282     cmsg = CMSG_FIRSTHDR(&msg);
283     for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
284       if (cmsg->cmsg_level == IPPROTO_IP
285         && cmsg->cmsg_type == IP_TTL) {
286           ttl = *(uint8_t *)CMSG_DATA(cmsg);
287           break;
288       }
289     };
290 
291     if (!FLAG(q)) {
292       if (FLAG(f)) xputc('\b');
293       else {
294         printf("%d bytes from %s: icmp_seq=%d ttl=%d", len, ntop(&srcaddr2.s),
295                ih->un.echo.sequence, ttl);
296         if (len >= sizeof(*ih)+4) printf(" time=%u ms", pkttime);
297         xputc('\n');
298       }
299     }
300 
301     toys.exitval = 0;
302   }
303 
304   sigatexit(0);
305   summary(0);
306 
307   if (CFG_TOYBOX_FREE) {
308     freeaddrinfo(ai2);
309     if (ifa2) freeifaddrs(ifa2);
310   }
311 }
312