toybox/toys/net/ping.c
<<
>>
Prefs
   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 < .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  // summary(0) gets called for us atexit.
 305  if (CFG_TOYBOX_FREE) {
 306    freeaddrinfo(ai2);
 307    if (ifa2) freeifaddrs(ifa2);
 308  }
 309}
 310