/* Copyright (C) 2001-2025 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ #include #include #include #include #include #include #include /*#include -- deliberately not including this */ #ifdef HAVE_NETINET_IP_VAR_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "ping_impl.h" #define NROUTES 9 /* number of record route slots */ #ifndef MAX_IPOPTLEN # define MAX_IPOPTLEN 40 #endif #include static int handler (int code, void *closure, struct sockaddr_in *dest, struct sockaddr_in *from, struct ip *ip, icmphdr_t * icmp, int datalen); int print_echo (int dup, struct ping_stat *stat, struct sockaddr_in *dest, struct sockaddr_in *from, struct ip *ip, icmphdr_t * icmp, int datalen); static int echo_finish (void); void print_icmp_header (struct sockaddr_in *from, struct ip *ip, icmphdr_t * icmp, int len); static void print_ip_opt (struct ip *ip, int hlen); int ping_echo (char *hostname) { #ifdef IP_OPTIONS char rspace[MAX_IPOPTLEN]; /* Maximal IP option space. */ #endif struct ping_stat ping_stat; int status; if (options & OPT_FLOOD && options & OPT_INTERVAL) error (EXIT_FAILURE, 0, "-f and -i incompatible options"); memset (&ping_stat, 0, sizeof (ping_stat)); ping_stat.tmin = 999999999.0; ping_set_type (ping, ICMP_ECHO); ping_set_packetsize (ping, data_length); ping_set_event_handler (ping, handler, &ping_stat); if (ping_set_dest (ping, hostname)) error (EXIT_FAILURE, 0, "unknown host"); if (options & OPT_RROUTE) { #ifdef IP_OPTIONS memset (rspace, 0, sizeof (rspace)); rspace[IPOPT_OPTVAL] = IPOPT_RR; rspace[IPOPT_OLEN] = sizeof (rspace) - 1; rspace[IPOPT_OFFSET] = IPOPT_MINOFF; if (setsockopt (ping->ping_fd, IPPROTO_IP, IP_OPTIONS, rspace, sizeof (rspace)) < 0) error (EXIT_FAILURE, errno, "setsockopt"); #else error (EXIT_FAILURE, 0, "record route not available in this " "implementation."); #endif /* IP_OPTIONS */ } else if (options & OPT_IPTIMESTAMP) { int type; if (suboptions & SOPT_TSPRESPEC) #ifdef IPOPT_TS_PRESPEC_RFC791 type = IPOPT_TS_PRESPEC_RFC791; #else type = IPOPT_TS_PRESPEC; #endif else if (suboptions & SOPT_TSADDR) type = IPOPT_TS_TSANDADDR; else type = IPOPT_TS_TSONLY; #ifdef IP_OPTIONS memset (rspace, 0, sizeof (rspace)); rspace[IPOPT_OPTVAL] = IPOPT_TS; rspace[IPOPT_OLEN] = sizeof (rspace); if (type != IPOPT_TS_TSONLY) rspace[IPOPT_OLEN] -= sizeof (n_time); /* Exsessive part. */ rspace[IPOPT_OFFSET] = IPOPT_MINOFF + 1; # ifdef IPOPT_POS_OV_FLG rspace[IPOPT_POS_OV_FLG] = type; # else rspace[3] = type; # endif/* !IPOPT_POS_OV_FLG */ if (setsockopt (ping->ping_fd, IPPROTO_IP, IP_OPTIONS, rspace, rspace[IPOPT_OLEN]) < 0) error (EXIT_FAILURE, errno, "setsockopt"); #else /* !IP_OPTIONS */ error (EXIT_FAILURE, 0, "IP timestamp not available in this " "implementation."); #endif /* IP_OPTIONS */ } printf ("PING %s (%s): %zu data bytes", ping->ping_hostname, inet_ntoa (ping->ping_dest.ping_sockaddr.sin_addr), data_length); if (options & OPT_VERBOSE) printf (", id 0x%04x = %u", ping->ping_ident, ping->ping_ident); printf ("\n"); status = ping_run (ping, echo_finish); free (ping->ping_hostname); return status; } int handler (int code, void *closure, struct sockaddr_in *dest, struct sockaddr_in *from, struct ip *ip, icmphdr_t *icmp, int datalen) { switch (code) { case PEV_RESPONSE: case PEV_DUPLICATE: print_echo (code == PEV_DUPLICATE, (struct ping_stat *) closure, dest, from, ip, icmp, datalen); break; case PEV_NOECHO:; print_icmp_header (from, ip, icmp, datalen); } return 0; } int print_echo (int dupflag, struct ping_stat *ping_stat, struct sockaddr_in *dest MAYBE_UNUSED, struct sockaddr_in *from, struct ip *ip, icmphdr_t *icmp, int datalen) { int hlen; bool timing = false; double triptime = 0.0; /* Length of IP header */ hlen = ip->ip_hl << 2; /* Length of ICMP header+payload */ datalen -= hlen; /* Do timing */ if (PING_TIMING (datalen - PING_HEADER_LEN)) { struct timeval tv; struct timespec ts; timing = true; /* Avoid unaligned data. */ memcpy (&tv, icmp->icmp_data, sizeof (tv)); /* *INDENT-OFF* */ ts = timespec_sub (current_timespec (), (struct timespec) { .tv_sec = tv.tv_sec, .tv_nsec = tv.tv_usec * 1000 }); /* *INDENT-ON* */ triptime = timespectod (ts) * 1000.0; ping_stat->tsum += triptime; ping_stat->tsumsq += triptime * triptime; if (triptime < ping_stat->tmin) ping_stat->tmin = triptime; if (triptime > ping_stat->tmax) ping_stat->tmax = triptime; } if (options & OPT_QUIET) return 0; if (options & OPT_FLOOD) { putchar ('\b'); return 0; } printf ("%d bytes from %s: icmp_seq=%u", datalen, inet_ntoa (*(struct in_addr *) &from->sin_addr.s_addr), ntohs (icmp->icmp_seq)); printf (" ttl=%d", ip->ip_ttl); if (timing) printf (" time=%.3f ms", triptime); if (dupflag) printf (" (DUP!)"); print_ip_opt (ip, hlen); printf ("\n"); return 0; } #define NITEMS(a) sizeof(a)/sizeof((a)[0]) struct icmp_diag { int type; char *text; void (*fun) (icmphdr_t *, void *data); void *data; }; struct icmp_code_descr { int type; int code; char *diag; } icmp_code_descr[] = { {ICMP_DEST_UNREACH, ICMP_NET_UNREACH, "Destination Net Unreachable"}, {ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, "Destination Host Unreachable"}, {ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, "Destination Protocol Unreachable"}, {ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, "Destination Port Unreachable"}, {ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, "Fragmentation needed and DF set"}, {ICMP_DEST_UNREACH, ICMP_SR_FAILED, "Source Route Failed"}, {ICMP_DEST_UNREACH, ICMP_NET_UNKNOWN, "Network Unknown"}, {ICMP_DEST_UNREACH, ICMP_HOST_UNKNOWN, "Host Unknown"}, {ICMP_DEST_UNREACH, ICMP_HOST_ISOLATED, "Host Isolated"}, {ICMP_DEST_UNREACH, ICMP_NET_UNR_TOS, "Destination Network Unreachable At This TOS"}, {ICMP_DEST_UNREACH, ICMP_HOST_UNR_TOS, "Destination Host Unreachable At This TOS"}, #ifdef ICMP_PKT_FILTERED {ICMP_DEST_UNREACH, ICMP_PKT_FILTERED, "Packet Filtered"}, #endif #ifdef ICMP_PREC_VIOLATION {ICMP_DEST_UNREACH, ICMP_PREC_VIOLATION, "Precedence Violation"}, #endif #ifdef ICMP_PREC_CUTOFF {ICMP_DEST_UNREACH, ICMP_PREC_CUTOFF, "Precedence Cutoff"}, #endif {ICMP_REDIRECT, ICMP_REDIR_NET, "Redirect Network"}, {ICMP_REDIRECT, ICMP_REDIR_HOST, "Redirect Host"}, {ICMP_REDIRECT, ICMP_REDIR_NETTOS, "Redirect Type of Service and Network"}, {ICMP_REDIRECT, ICMP_REDIR_HOSTTOS, "Redirect Type of Service and Host"}, {ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, "Time to live exceeded"}, {ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, "Frag reassembly time exceeded"} }; static void print_icmp_code (int type, int code, char *prefix) { struct icmp_code_descr *p; for (p = icmp_code_descr; p < icmp_code_descr + NITEMS (icmp_code_descr); p++) if (p->type == type && p->code == code) { printf ("%s\n", p->diag); return; } printf ("%s, Unknown Code: %d\n", prefix, code); } static void print_ip_header (struct ip *ip) { size_t hlen; unsigned char *cp; hlen = ip->ip_hl << 2; cp = (unsigned char *) ip + sizeof (*ip); /* point to options */ if (options & OPT_VERBOSE) { size_t j; printf ("IP Hdr Dump:\n "); for (j = 0; j < sizeof (*ip); ++j) printf ("%02x%s", *((unsigned char *) ip + j), (j % 2) ? " " : ""); /* Group bytes two by two. */ printf ("\n"); } printf ("Vr HL TOS Len ID Flg off TTL Pro cks Src\tDst\tData\n"); printf (" %1x %1x %02x", ip->ip_v, ip->ip_hl, ip->ip_tos); /* * The member `ip_len' is not portably reported in any byte order. * Use a simple heuristic to print a reasonable value. */ printf (" %04x %04x", (ip->ip_len > 0x2000) ? ntohs (ip->ip_len) : ip->ip_len, ntohs (ip->ip_id)); printf (" %1x %04x", (ntohs (ip->ip_off) & 0xe000) >> 13, ntohs (ip->ip_off) & 0x1fff); printf (" %02x %02x %04x", ip->ip_ttl, ip->ip_p, ntohs (ip->ip_sum)); printf (" %s ", inet_ntoa (*((struct in_addr *) &ip->ip_src))); printf (" %s ", inet_ntoa (*((struct in_addr *) &ip->ip_dst))); while (hlen-- > sizeof (*ip)) printf ("%02x", *cp++); printf ("\n"); } void print_ip_data (icmphdr_t *icmp, void *data MAYBE_UNUSED) { int hlen; unsigned char *cp; struct ip *ip = &icmp->icmp_ip; print_ip_header (ip); hlen = ip->ip_hl << 2; cp = (unsigned char *) ip + hlen; if (ip->ip_p == IPPROTO_TCP) printf ("TCP: from port %u, to port %u (decimal)\n", (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3))); else if (ip->ip_p == IPPROTO_UDP) printf ("UDP: from port %u, to port %u (decimal)\n", (*cp * 256 + *(cp + 1)), (*(cp + 2) * 256 + *(cp + 3))); else if (ip->ip_p == IPPROTO_ICMP) { int type = *cp; int code = *(cp + 1); printf ("ICMP: type %u, code %u, size %u", type, code, ntohs (ip->ip_len) - hlen); if (type == ICMP_ECHOREPLY || type == ICMP_ECHO) printf (", id 0x%04x, seq 0x%04x", *(cp + 4) * 256 + *(cp + 5), *(cp + 6) * 256 + *(cp + 7)); printf ("\n"); } } static void print_icmp (icmphdr_t *icmp, void *data) { print_icmp_code (icmp->icmp_type, icmp->icmp_code, data); if (options & OPT_VERBOSE) print_ip_data (icmp, NULL); } static void print_parameterprob (icmphdr_t *icmp, void *data) { printf ("Parameter problem: IP address = %s\n", inet_ntoa (icmp->icmp_gwaddr)); print_ip_data (icmp, data); } struct icmp_diag icmp_diag[] = { {ICMP_ECHOREPLY, "Echo Reply", NULL, NULL}, {ICMP_DEST_UNREACH, NULL, print_icmp, "Dest Unreachable"}, {ICMP_SOURCE_QUENCH, "Source Quench", print_ip_data, NULL}, {ICMP_REDIRECT, NULL, print_icmp, "Redirect"}, {ICMP_ECHO, "Echo Request", NULL, NULL}, {ICMP_ROUTERADV, "Router Advertisement", NULL, NULL}, {ICMP_ROUTERDISCOVERY, "Router Discovery", NULL, NULL}, {ICMP_TIME_EXCEEDED, NULL, print_icmp, "Time exceeded"}, {ICMP_PARAMETERPROB, NULL, print_parameterprob, NULL}, {ICMP_TIMESTAMP, "Timestamp", NULL, NULL}, {ICMP_TIMESTAMPREPLY, "Timestamp Reply", NULL, NULL}, {ICMP_INFO_REQUEST, "Information Request", NULL, NULL}, {ICMP_INFO_REPLY, "Information Reply", NULL, NULL}, {ICMP_ADDRESS, "Address Mask Request", NULL, NULL}, {ICMP_ADDRESSREPLY, "Address Mask Reply", NULL, NULL}, }; void print_icmp_header (struct sockaddr_in *from, struct ip *ip, icmphdr_t *icmp, int len) { int hlen; struct ip *orig_ip; char *s; struct icmp_diag *p; /* Length of the IP header */ hlen = ip->ip_hl << 2; /* Original IP header */ orig_ip = &icmp->icmp_ip; if (!(options & OPT_VERBOSE || orig_ip->ip_dst.s_addr == ping->ping_dest.ping_sockaddr.sin_addr.s_addr)) return; s = ipaddr2str ((struct sockaddr *) from, sizeof (*from)); printf ("%d bytes from %s: ", len - hlen, s); free (s); for (p = icmp_diag; p < icmp_diag + NITEMS (icmp_diag); p++) { if (p->type == icmp->icmp_type) { if (p->text) printf ("%s\n", p->text); if (p->fun) p->fun (icmp, p->data); return; } } printf ("Bad ICMP type: %d\n", icmp->icmp_type); } void print_ip_opt (struct ip *ip, int hlen) { unsigned char *cp; int i, j, k, l; static int old_rrlen; static char old_rr[MAX_IPOPTLEN]; cp = (unsigned char *) (ip + 1); for (; hlen > (int) sizeof (struct ip); --hlen, ++cp) switch (*cp) { case IPOPT_EOL: hlen = 0; break; case IPOPT_LSRR: case IPOPT_SSRR: printf ("\n%cSRR: ", (*cp == IPOPT_LSRR) ? 'L' : 'S'); hlen -= 2; j = *++cp; ++cp; if (j > IPOPT_MINOFF) for (;;) { /* Fetch in network byte order, calculate as host. */ l = *++cp; l = (l << 8) + *++cp; l = (l << 8) + *++cp; l = (l << 8) + *++cp; if (l == 0) { printf ("\t0.0.0.0"); } else { struct in_addr ina; char *s; ina.s_addr = htonl (l); printf ("\t%s", s = sinaddr2str (ina)); free (s); } hlen -= 4; j -= 4; if (j <= IPOPT_MINOFF) break; putchar ('\n'); } break; case IPOPT_RR: j = *++cp; i = *++cp; hlen -= 2; if (i > j) i = j; i -= IPOPT_MINOFF; if (i <= 0) break; if (i == old_rrlen && cp == (unsigned char *) (ip + 1) + 2 && !memcmp ((char *) cp, old_rr, i) && !(options & OPT_FLOOD)) { printf ("\t (same route)"); i = ((i + 3) / 4) * 4; hlen -= i; cp += i; break; } if (i < MAX_IPOPTLEN) { old_rrlen = i; memmove (old_rr, cp, i); } else old_rrlen = 0; printf ("\nRR: "); j = 0; for (;;) { /* Fetch in network byte order, calculate as host. */ l = *++cp; l = (l << 8) + *++cp; l = (l << 8) + *++cp; l = (l << 8) + *++cp; if (l == 0) { printf ("\t0.0.0.0"); } else { struct in_addr ina; char *s; ina.s_addr = htonl (l); printf ("\t%s", s = sinaddr2str (ina)); free (s); } hlen -= 4; i -= 4; j += 4; if (i <= 0) break; if (j >= MAX_IPOPTLEN) { printf ("\t (truncated route)"); break; } putchar ('\n'); } break; case IPOPT_TS: j = *++cp; /* len */ i = *++cp; /* ptr */ hlen -= 2; if (i > j) i = j; /* Check minimal sizing. */ if (j <= (int) (IPOPT_MINOFF + sizeof (n_time))) break; k = *++cp; /* OV, FL */ ++cp; /* Points at first content. */ hlen -= 2; printf ("\nTS:"); j = 5; /* First possible slot. */ for (;;) { char timestr[16]; if ((k & 0x0f) != IPOPT_TS_TSONLY && ((j / 4) % 2 == 1)) /* find 5, 13, 21, 29 */ { /* IP addresses */ struct in_addr ina; char *s; ina.s_addr = *((in_addr_t *) cp); printf ("\t%s", s = sinaddr2str (ina)); free (s); hlen -= sizeof (in_addr_t); cp += sizeof (in_addr_t); j += sizeof (in_addr_t); } else { /* Timestamps */ printf ("\t%s ms", ping_cvt_time (timestr, sizeof (timestr), ntohl (*(n_time *) cp))); if (options & OPT_VERBOSE) printf (" = 0x%08x", ntohl (*(n_time *) cp)); hlen -= sizeof (n_time); cp += sizeof (n_time); j += sizeof (n_time); putchar ('\n'); } if (j >= i) break; } if (k & 0xf0) printf ("\t(%u overflowing hosts)", k >> 4); break; case IPOPT_NOP: printf ("\nNOP"); break; default: printf ("\nunknown option %x", *cp); break; } } int echo_finish (void) { ping_finish (); if (ping->ping_num_recv && PING_TIMING (data_length)) { struct ping_stat *ping_stat = (struct ping_stat *) ping->ping_closure; double total = ping->ping_num_recv + ping->ping_num_rept; double avg = ping_stat->tsum / total; double vari = ping_stat->tsumsq / total - avg * avg; printf ("round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms\n", ping_stat->tmin, avg, ping_stat->tmax, nsqrt (vari, 0.0005)); } return (ping->ping_num_recv == 0); }