master xplshn/aruu / cmd / net / ping.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include "arg.h"
  4#include "util.h"
  5
  6#include <arpa/inet.h>
  7#include <errno.h>
  8#include <netdb.h>
  9#include <netinet/in.h>
 10#include <netinet/ip.h>
 11#include <netinet/ip_icmp.h>
 12#include <poll.h>
 13#include <signal.h>
 14#include <stdio.h>
 15#include <stdlib.h>
 16#include <string.h>
 17#include <sys/socket.h>
 18#include <sys/time.h>
 19#include <sys/types.h>
 20#include <unistd.h>
 21
 22static int keep_running = 1;
 23
 24static void
 25usage(void)
 26{
 27  eprintf(
 28      "usage: %s [-c count] [-i interval] [-s size] [-t ttl] [-w "
 29      "deadline] [-q] host\n",
 30      argv0
 31  );
 32}
 33
 34static void
 35sigint_handler(int sig)
 36{
 37  (void)sig;
 38  keep_running = 0;
 39}
 40
 41static unsigned short
 42checksum(unsigned short *addr, int len)
 43{
 44  unsigned long sum = 0;
 45
 46  while (len > 1) {
 47    sum += *addr++;
 48    len -= 2;
 49  }
 50  if (len > 0)
 51    sum += *(unsigned char *)addr;
 52  while (sum >> 16)
 53    sum = (sum & 0xffff) + (sum >> 16);
 54  return ~sum;
 55}
 56
 57static void
 58send_ping(int sock, struct sockaddr_in *dst, int seq, int size)
 59{
 60  struct icmphdr *icmp;
 61  char           *packet;
 62  struct timeval  tv;
 63  int             packlen = sizeof(*icmp) + size;
 64
 65  packet = emalloc(packlen);
 66  memset(packet, 0, packlen);
 67
 68  icmp                   = (struct icmphdr *)packet;
 69  icmp->type             = ICMP_ECHO;
 70  icmp->code             = 0;
 71  icmp->un.echo.id       = htons(getpid() & 0xffff);
 72  icmp->un.echo.sequence = htons(seq);
 73
 74  /* store timestamp in payload if size is large enough */
 75  if (size >= (int)sizeof(struct timeval)) {
 76    gettimeofday(&tv, NULL);
 77    memcpy(packet + sizeof(*icmp), &tv, sizeof(tv));
 78  }
 79
 80  icmp->checksum = checksum((unsigned short *)packet, packlen);
 81
 82  if (sendto(sock, packet, packlen, 0, (struct sockaddr *)dst, sizeof(*dst)) < 0)
 83    eprintf("sendto:");
 84
 85  free(packet);
 86}
 87
 88static double
 89get_time_ms(void)
 90{
 91  struct timeval tv;
 92  gettimeofday(&tv, NULL);
 93  return (double)tv.tv_sec * 1000.0 + (double)tv.tv_usec / 1000.0;
 94}
 95
 96static void
 97print_stats(
 98    const char *host, int sent, int received, double min_rtt, double max_rtt, double sum_rtt
 99)
100{
101  printf("\n--- %s ping statistics ---\n", host);
102  printf(
103      "%d packets transmitted, %d received, %d%% packet loss\n",
104      sent,
105      received,
106      sent > 0 ? (sent - received) * 100 / sent : 0
107  );
108  if (received > 0) {
109    printf("rtt min/avg/max = %.3f/%.3f/%.3f ms\n", min_rtt, sum_rtt / received, max_rtt);
110  }
111}
112
113// ?man ping: send icmp echo requests
114// ?man arguments: host
115// ?man send icmp echo requests to verify network connectivity
116int
117main(int argc, char *argv[])
118{
119  struct addrinfo    hints, *res;
120  struct sockaddr_in dst;
121  struct sockaddr_in from;
122  struct pollfd      pfd;
123  struct timeval     sent_tv;
124  struct icmphdr    *icmp;
125  char              *cflag = NULL;
126  char              *iflag = NULL;
127  char              *sflag = NULL;
128  char              *tflag = NULL;
129  char              *wflag = NULL;
130  char              *host;
131  char              *rxbuf;
132  int                qflag    = 0;
133  int                count    = 0;
134  int                size     = 56;
135  int                ttl      = 0;
136  int                deadline = 0;
137  int                sock     = -1;
138  int                is_raw   = 1;
139  int                seq      = 1;
140  int                sent     = 0;
141  int                received = 0;
142  int                r, optval, p_res, hlen;
143  double             interval = 1.0;
144  double             min_rtt  = 999999.0;
145  double             max_rtt  = 0.0;
146  double             sum_rtt  = 0.0;
147  double    start_time, deadline_ms, next_send_ms, now, timeout_ms, time_to_deadline, rtt, sent_ms;
148  ssize_t   n;
149  socklen_t fromlen;
150
151  ARGBEGIN
152  {
153    // ?man -c:str: print count or perform stdout action
154    case 'c':
155      cflag = EARGF(usage());
156      break;
157    // ?man -i:str: interactive mode or prompt for confirmation
158    case 'i':
159      iflag = EARGF(usage());
160      break;
161    // ?man -s:str: silent mode or print summary
162    case 's':
163      sflag = EARGF(usage());
164      break;
165    // ?man -t:str: sort or specify timestamp
166    case 't':
167      tflag = EARGF(usage());
168      break;
169    // ?man -w:str: wait for completion
170    case 'w':
171      wflag = EARGF(usage());
172      break;
173    // ?man -q: quiet mode; suppress output
174    case 'q':
175      qflag = 1;
176      break;
177    default:
178      usage();
179  }
180  ARGEND
181
182  if (argc < 1)
183    usage();
184
185  host = argv[0];
186
187  if (cflag)
188    count = estrtonum(cflag, 1, 1000000);
189  if (iflag)
190    interval = estrtod(iflag);
191  if (sflag)
192    size = estrtonum(sflag, 0, 65507);
193  if (tflag)
194    ttl = estrtonum(tflag, 1, 255);
195  if (wflag)
196    deadline = estrtonum(wflag, 1, 1000000);
197
198  memset(&hints, 0, sizeof(hints));
199  hints.ai_family   = AF_INET;
200  hints.ai_socktype = SOCK_RAW;
201
202  r = getaddrinfo(host, NULL, &hints, &res);
203  if (r != 0)
204    eprintf("getaddrinfo %s: %s\n", host, gai_strerror(r));
205
206  memcpy(&dst, res->ai_addr, sizeof(dst));
207  freeaddrinfo(res);
208
209  /* try opening raw icmp socket first, fallback to dgram */
210  sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
211  if (sock < 0) {
212    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
213    if (sock < 0)
214      eprintf("socket:");
215    is_raw = 0;
216  }
217
218  if (ttl > 0) {
219    if (setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0)
220      eprintf("setsockopt IP_TTL:");
221  }
222
223  /* request that recvfrom returns the ttl value */
224  optval = 1;
225  if (is_raw) {
226    setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &optval, sizeof(optval));
227  }
228
229  if (!qflag) {
230    printf("PING %s (%s) %d bytes of data.\n", host, inet_ntoa(dst.sin_addr), size);
231  }
232
233  signal(SIGINT, sigint_handler);
234
235  pfd.fd     = sock;
236  pfd.events = POLLIN;
237
238  start_time   = get_time_ms();
239  deadline_ms  = deadline > 0 ? start_time + deadline * 1000.0 : 0.0;
240  next_send_ms = start_time;
241
242  rxbuf = emalloc(size + 1024);
243
244  while (keep_running) {
245    now = get_time_ms();
246
247    if (deadline > 0 && now >= deadline_ms)
248      break;
249
250    if (count > 0 && sent >= count && received >= sent)
251      break;
252
253    if (now >= next_send_ms && (count == 0 || sent < count)) {
254      send_ping(sock, &dst, seq++, size);
255      sent++;
256      next_send_ms = now + interval * 1000.0;
257    }
258
259    timeout_ms = next_send_ms - now;
260    if (deadline > 0) {
261      time_to_deadline = deadline_ms - now;
262      if (time_to_deadline < timeout_ms)
263        timeout_ms = time_to_deadline;
264    }
265    if (timeout_ms < 0)
266      timeout_ms = 0;
267
268    if (count > 0 && sent >= count)
269      timeout_ms = 2000.0;
270
271    p_res = poll(&pfd, 1, (int)timeout_ms);
272    if (p_res < 0) {
273      if (errno == EINTR)
274        continue;
275      eprintf("poll:\n");
276    }
277
278    if (p_res > 0 && (pfd.revents & POLLIN)) {
279      fromlen = sizeof(from);
280      n       = recvfrom(sock, rxbuf, size + 1024, 0, (struct sockaddr *)&from, &fromlen);
281      if (n < 0) {
282        if (errno == EINTR || errno == EAGAIN)
283          continue;
284        eprintf("recvfrom:\n");
285      }
286
287      hlen = 0;
288      if (is_raw) {
289        struct ip *ip = (struct ip *)rxbuf;
290        hlen          = ip->ip_hl << 2;
291        if (n < hlen + (int)sizeof(*icmp))
292          continue;
293      }
294      icmp = (struct icmphdr *)(rxbuf + hlen);
295
296      if (icmp->type == ICMP_ECHOREPLY
297          && (!is_raw || ntohs(icmp->un.echo.id) == (getpid() & 0xffff))) {
298        received++;
299        rtt = get_time_ms();
300        if (n - hlen - (int)sizeof(*icmp) >= (int)sizeof(struct timeval)) {
301          memcpy(&sent_tv, rxbuf + hlen + sizeof(*icmp), sizeof(sent_tv));
302          sent_ms = (double)sent_tv.tv_sec * 1000.0 + (double)sent_tv.tv_usec / 1000.0;
303          rtt -= sent_ms;
304          if (rtt < min_rtt)
305            min_rtt = rtt;
306          if (rtt > max_rtt)
307            max_rtt = rtt;
308          sum_rtt += rtt;
309          if (!qflag) {
310            printf(
311                "%d bytes from %s: "
312                "icmp_seq=%d ttl=%d "
313                "time=%.3f ms\n",
314                (int)n,
315                inet_ntoa(from.sin_addr),
316                ntohs(icmp->un.echo.sequence),
317                is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64,
318                rtt
319            );
320          }
321        } else {
322          if (!qflag) {
323            printf(
324                "%d bytes from %s: "
325                "icmp_seq=%d ttl=%d\n",
326                (int)n,
327                inet_ntoa(from.sin_addr),
328                ntohs(icmp->un.echo.sequence),
329                is_raw ? ((struct ip *)rxbuf)->ip_ttl : 64
330            );
331          }
332        }
333      }
334    }
335  }
336
337  free(rxbuf);
338  close(sock);
339
340  print_stats(host, sent, received, min_rtt, max_rtt, sum_rtt);
341
342  return received > 0 ? 0 : 1;
343}