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 14USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4064=56i%W#<0=3w#<0qf46I:[-46]", TOYFLAG_USR|TOYFLAG_BIN)) 15USE_PING(OLDTOY(ping6, ping, TOYFLAG_USR|TOYFLAG_BIN)) 16 17config 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 < 0.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 49GLOBALS( 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. 59static 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 73static 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 88static 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 110void 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 && geteuid()) 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