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 $(((1<<31)-1)) > /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 > 4088 = sizeof(toybuf)-sizeof(struct icmphdr), then kernel adds 20 bytes
  14USE_PING(NEWTOY(ping, "<1>1m#t#<0>255=64c#<0=3s#<0>4088=56I:i:W#<0=10w#<0qf46[-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 -c (default 10)
  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  long w;
  51  long W;
  52  char *i;
  53  char *I;
  54  long s;
  55  long c;
  56  long t;
  57  long m;
  58
  59  struct sockaddr *sa;
  60  int sock;
  61  long i_ms;
  62  unsigned long sent, recv, fugit, min, max;
  63)
  64
  65static void xsendto(int sockfd, void *buf, size_t len, struct sockaddr *dest)
  66{
  67  int rc = sendto(TT.sock, buf, len, 0, dest,
  68    dest->sa_family == AF_INET ? sizeof(struct sockaddr_in) :
  69      sizeof(struct sockaddr_in6));
  70
  71  if (rc != len) perror_exit("sendto");
  72}
  73
  74static void summary(int sig)
  75{
  76  if (!(toys.optflags&FLAG_q) && TT.sent && TT.sa) {
  77    printf("\n--- %s ping statistics ---\n", ntop(TT.sa));
  78    printf("%lu packets transmitted, %lu received, %ld%% packet loss\n",
  79      TT.sent, TT.recv, ((TT.sent-TT.recv)*100)/TT.sent);
  80    printf("round-trip min/avg/max = %lu/%lu/%lu ms\n",
  81      TT.min, TT.max, TT.fugit/TT.recv);
  82  }
  83  TT.sa = 0;
  84}
  85
  86// assumes aligned and can read even number of bytes
  87static unsigned short pingchksum(unsigned short *data, int len)
  88{
  89  unsigned short u = 0, d;
  90
  91  // circular carry is endian independent: bits from high byte go to low byte
  92  while (len>0) {
  93    d = *data++;
  94    if (len == 1) d &= 255<<IS_BIG_ENDIAN;
  95    if (d >= (u += d)) u++;
  96    len -= 2;
  97  }
  98
  99  return u;
 100}
 101
 102void ping_main(void)
 103{
 104  struct addrinfo *ai, *ai2;
 105  struct ifaddrs *ifa, *ifa2 = 0;
 106  union {
 107    struct sockaddr_in in;
 108    struct sockaddr_in6 in6;
 109  } src_addr, src_addr2;
 110  struct sockaddr *sa = (void *)&src_addr, *sa2 = (void *)&src_addr2;
 111  struct pollfd pfd;
 112  int family = 0, len;
 113  long long tnext, tW, tnow, tw;
 114  unsigned short seq = 0, pkttime;
 115  struct icmphdr *ih = (void *)toybuf;
 116
 117  // Interval
 118  if (TT.i) {
 119    long frac;
 120
 121    TT.i_ms = xparsetime(TT.i, 1000, &frac) * 1000;
 122    TT.i_ms += frac;
 123    if (TT.i_ms<200 && getuid()) error_exit("need root for -i <200");
 124  } else TT.i_ms = (toys.optflags&FLAG_f) ? 200 : 1000;
 125  if (!(toys.optflags&FLAG_s)) TT.s = 56; // 64-PHDR_LEN
 126  if ((toys.optflags&(FLAG_f|FLAG_c)) == FLAG_f) TT.c = 15;
 127
 128  // ipv4 or ipv6? (0 = autodetect if -I or arg have only one address type.)
 129  if ((toys.optflags&FLAG_6) || toys.which->name[4] == '6') family = AF_INET6;
 130  else if (toys.optflags&FLAG_4) family = AF_INET;
 131  else family = 0;
 132
 133  sigatexit(summary);
 134
 135  // If -I src_addr look it up. Allow numeric address of correct type.
 136  memset(&src_addr, 0, sizeof(src_addr));
 137  if (TT.I) {
 138    if (!(toys.optflags&FLAG_6) && inet_pton(AF_INET, TT.I,
 139      (void *)&src_addr.in.sin_addr))
 140        family = AF_INET;
 141    else if (!(toys.optflags&FLAG_4) && inet_pton(AF_INET6, TT.I,
 142      (void *)&src_addr.in6.sin6_addr))
 143        family = AF_INET6;
 144    else if (getifaddrs(&ifa2)) perror_exit("getifaddrs");
 145  }
 146
 147  // Look up HOST address, filtering for correct type and interface.
 148  // If -I but no -46 then find compatible type between -I and HOST
 149  ai2 = xgetaddrinfo(*toys.optargs, 0, family, 0, 0, 0);
 150  for (ai = ai2; ai; ai = ai->ai_next) {
 151
 152    // correct type?
 153    if (family && family!=ai->ai_family) continue;
 154    if (ai->ai_family!=AF_INET && ai->ai_family!=AF_INET6) continue;
 155
 156    // correct interface?
 157    if (!TT.I || !ifa2) break;
 158    for (ifa = ifa2; ifa; ifa = ifa->ifa_next) {
 159      if (!ifa->ifa_addr || ifa->ifa_addr->sa_family!=ai->ai_family
 160          || strcmp(ifa->ifa_name, TT.I)) continue;
 161      sa = (void *)ifa->ifa_addr;
 162
 163      break;
 164    }
 165    if (ifa) break;
 166  }
 167
 168  if (!ai)
 169    error_exit("no v%d addr for -I %s", 4+2*(family==AF_INET6), TT.I);
 170  TT.sa = ai->ai_addr;
 171
 172  // Open DGRAM socket
 173  sa->sa_family = ai->ai_family;
 174  TT.sock = socket(ai->ai_family, SOCK_DGRAM,
 175    len = (ai->ai_family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6);
 176  if (TT.sock == -1) {
 177    perror_msg("socket SOCK_DGRAM %x", len);
 178    if (errno == EACCES) {
 179      fprintf(stderr, "Kernel bug workaround (as root):\n");
 180      fprintf(stderr, "echo 0 9999999 > /proc/sys/net/ipv4/ping_group_range\n");
 181    }
 182    xexit();
 183  }
 184  if (TT.I && bind(TT.sock, sa, sizeof(src_addr))) perror_exit("bind");
 185
 186  if (toys.optflags&FLAG_m) {
 187      int mark = TT.m;
 188
 189      xsetsockopt(TT.sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark));
 190  }
 191
 192  if (TT.t) {
 193    len = TT.t;
 194
 195    if (ai->ai_family == AF_INET)
 196      xsetsockopt(TT.sock, IPPROTO_IP, IP_TTL, &len, 4);
 197    else xsetsockopt(TT.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &len, 4);
 198  }
 199
 200  if (!(toys.optflags&FLAG_q)) {
 201    printf("Ping %s (%s)", *toys.optargs, ntop(TT.sa));
 202    if (TT.I) {
 203      *toybuf = 0;
 204      printf(" from %s (%s)", TT.I, ntop(sa));
 205    }
 206    // 20 byte TCP header, 8 byte ICMP header, plus data payload
 207    printf(": %ld(%ld) bytes.\n", TT.s, TT.s+28);
 208  }
 209  toys.exitval = 1;
 210
 211  tW = tw = 0;
 212  tnext = millitime();
 213  if (TT.w) tw = TT.w*1000+tnext;
 214
 215  // Send/receive packets
 216  for (;;) {
 217    int waitms = INT_MAX;
 218
 219    // Exit due to timeout? (TODO: timeout is after last packet, waiting if
 220    // any packets ever dropped. Not timeout since packet was dropped.)
 221    tnow = millitime();
 222    if (tW) if (0>=(waitms = tW-tnow) || !(TT.sent-TT.recv)) break;
 223    if (tw) {
 224      if (tnow>tw) break;
 225      else if (waitms>tw-tnow) waitms = tw-tnow;
 226    // Time to send the next packet?
 227    } else if (tnext-tnow <= 0) {
 228      tnext += TT.i_ms;
 229
 230      memset(ih, 0, sizeof(*ih));
 231      ih->type = (ai->ai_family == AF_INET) ? 8 : 128;
 232      ih->un.echo.id = getpid();
 233      ih->un.echo.sequence = ++seq;
 234      if (TT.s >= 4) *(unsigned *)(ih+1) = tnow;
 235
 236      ih->checksum = 0;
 237      ih->checksum = pingchksum((void *)toybuf, TT.s+sizeof(*ih));
 238      xsendto(TT.sock, toybuf, TT.s+sizeof(*ih), TT.sa);
 239      TT.sent++;
 240      if ((toys.optflags&(FLAG_f|FLAG_q)) == FLAG_f) xputc('.');
 241
 242      // last packet?
 243      if (TT.c) if (!--TT.c) {
 244        if (!TT.W) break;
 245        tW = tnow + TT.W*1000;
 246      }
 247    }
 248
 249    // This is down here so it's against new period if we just sent a packet
 250    if (!tw && waitms>tnext-tnow) waitms = tnext-tnow;
 251
 252    // wait for next packet or timeout
 253
 254    if (waitms<0) waitms = 0;
 255    pfd.fd = TT.sock;
 256    pfd.events = POLLIN;
 257    if (0>(len = poll(&pfd, 1, waitms))) break;
 258    if (!len) continue;
 259
 260    len = sizeof(src_addr2);
 261    len = recvfrom(TT.sock, toybuf, sizeof(toybuf), 0, sa2, (void *)&len);
 262    TT.recv++;
 263    TT.fugit += (pkttime = millitime()-*(unsigned *)(ih+1));
 264
 265    // reply id == 0 for ipv4, 129 for ipv6
 266
 267    if (!(toys.optflags&FLAG_q)) {
 268      if (toys.optflags&FLAG_f) xputc('\b');
 269      else {
 270        printf("%d bytes from %s: icmp_seq=%d ttl=%d", len, ntop(sa2),
 271               ih->un.echo.sequence, 0);
 272        if (len >= sizeof(*ih)+4)
 273          printf(" time=%u ms", pkttime);
 274        xputc('\n');
 275      }
 276    }
 277
 278    toys.exitval = 0;
 279  }
 280
 281  summary(0);
 282
 283  if (CFG_TOYBOX_FREE) {
 284    freeaddrinfo(ai2);
 285    if (ifa2) freeifaddrs(ifa2);
 286  }
 287}
 288