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