]> git.gsnw.org Git - fping.git/commitdiff
fix ICMPv6 error message handling and reporting
authorErik Auerswald <auerswal@unix-ag.uni-kl.de>
Sun, 4 May 2025 15:44:29 +0000 (17:44 +0200)
committerErik Auerswald <auerswal@unix-ag.uni-kl.de>
Sun, 13 Jul 2025 13:42:00 +0000 (15:42 +0200)
Handling of received ICMPv6 messages different from Echo Reply
was based on IPv4 code.  It would thus use a too small minimum
size, and interpret the wrong data as ICMPv6 fields.  Thus
received ICMPv6 error messages were either not reported at all,
or were reported incorrectly.

This commit introduces the following changes to fix this and thus
address GitHub issue #390:

- Filter out irrelevant ICMPv6 messages: fping needs to receive
  ICMPv6 Echo Reply and ICMPv6 error messages, nothing else
  (only relevant for raw sockets).
- Fix offset calculation to look at the included packet that
  caused this ICMPv6 error message.
- Ignore invoking packets that do not have ICMPv6 as the first
  IPv6 Next Header in the packet (there could be, e.g., IPv6
  Extension Headers).
- Fix ICMPv6 message type determination, and print respective
  warning messages.

CHANGELOG.md
configure.ac
src/fping.c
src/socket6.c

index 8d66a27a9726c96d57de25257546f6b5a75c4bd9..b4039549ac50fdf686d1d01a111446c28209d502 100644 (file)
@@ -26,6 +26,8 @@ Next
 - Options --print-tos and --print-ttl now also work for IPv6, and no
   longer require privileges (#384, thanks @auerswal)
 
+- Report received ICMPv6 error messages (#391, thanks @auerswal)
+
 fping 5.3 (2025-01-02)
 ======================
 
index 80d05fcf5650c6c2a78fa80b396b850a464191e5..99a869728b437a7c5aef17495859190c04f6d696 100644 (file)
@@ -34,7 +34,14 @@ AC_ARG_ENABLE([ipv6],
   AS_HELP_STRING([--disable-ipv6], [Disable support for pinging IPv6 hosts]))
 AS_IF([test "x$enable_ipv6" != "xno"], [
     dnl Test if IPv6 is supported
-       AC_CHECK_HEADERS([netinet/icmp6.h], [have_ipv6="yes"], [], [[
+       AC_CHECK_HEADERS([netinet/icmp6.h], [have_ipv6="yes"], [have_ipv6="no"], [[
+      #include <netinet/in.h>
+      #include <sys/types.h>
+  ]])
+])
+AS_IF([test "x$have_ipv6" == "xyes"], [
+    dnl Test if IPv6 is supported
+       AC_CHECK_HEADERS([netinet/ip6.h], [have_ipv6="yes"], [have_ipv6="no"], [[
       #include <netinet/in.h>
       #include <sys/types.h>
   ]])
index bb60a666c2f201272e70b5110495fcf5d52231ac..00cfdb4ff93c3f6999352c0e891a898f579e69d7 100644 (file)
@@ -70,6 +70,7 @@ extern "C" {
 
 #ifdef IPV6
 #include <netinet/icmp6.h>
+#include <netinet/ip6.h>
 #endif
 #include <netinet/in_systm.h>
 
@@ -218,6 +219,46 @@ char *icmp_unreach_str[16] = {
 
 #define ICMP_UNREACH_MAXTYPE 15
 
+#ifdef IPV6
+/* Long names for ICMPv6 unreachable codes */
+#define ICMP6_UNREACH_MAXCODE 9
+char *icmp6_unreach_str[ICMP6_UNREACH_MAXCODE + 1] = {
+    "No route to destination", /* 0 */
+    "Communication with destination administratively prohibited", /* 1 */
+    "Beyond scope of source address", /* 2 */
+    "Address unreachable", /* 3 */
+    "Port unreachable", /* 4 */
+    "Source address failed ingress/egress policy", /* 5 */
+    "Reject route to destination", /* 6 */
+    "Error in Source Routing Header", /* 7 */
+    "Headers too long", /* 8 */
+    "Error in P-Route", /* 9 */
+};
+
+/* Long names for ICMPv6 time exceeded codes */
+#define ICMP6_TIME_EXCEEDED_MAXCODE 1
+char *icmp6_time_exceeded_str[ICMP6_TIME_EXCEEDED_MAXCODE + 1] = {
+    "Hop limit exceeded in transit", /* 0 */
+    "Fragment reassembly time exceeded", /* 1 */
+};
+
+/* Long names for ICMPv6 parameter problem codes */
+#define ICMP6_PARAM_PROB_MAXCODE 10
+char *icmp6_param_prob_str[ICMP6_PARAM_PROB_MAXCODE + 1] = {
+    "Erroneous header field encountered", /* 0 */
+    "Unrecognized Next Header type encountered", /* 1 */
+    "Unrecognized IPv6 option encountered", /* 2 */
+    "IPv6 First Fragment has incomplete IPv6 Header Chain", /* 3 */
+    "SR Upper-layer Header Error", /* 4 */
+    "Unrecognized Next Header type encountered by intermediate node", /* 5 */
+    "Extension header too big", /* 6 */
+    "Extension header chain too long", /* 7 */
+    "Too many extension headers", /* 8 */
+    "Too many options in extension header", /* 9 */
+    "Option too big", /* 10 */
+};
+#endif
+
 struct event;
 typedef struct host_entry {
     int i; /* index into array */
@@ -2864,7 +2905,7 @@ int decode_icmp_ipv6(
         if (verbose_flag) {
             char buf[INET6_ADDRSTRLEN];
             getnameinfo(response_addr, response_addr_len, buf, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
-            printf("received packet too short for ICMP (%d bytes from %s)\n", (int)reply_buf_len, buf);
+            printf("received packet too short for ICMPv6 (%d bytes from %s)\n", (int)reply_buf_len, buf);
         }
         return 0; /* too short */
     }
@@ -2872,21 +2913,31 @@ int decode_icmp_ipv6(
     icp = (struct icmp6_hdr *)reply_buf;
 
     if (icp->icmp6_type != ICMP6_ECHO_REPLY) {
-        /* Handle other ICMP packets */
+        /* Handle other ICMPv6 packets */
+        struct ip6_hdr *sent_ipv6;
         struct icmp6_hdr *sent_icmp;
         SEQMAP_VALUE *seqmap_value;
         char addr_ascii[INET6_ADDRSTRLEN];
         HOST_ENTRY *h;
 
-        /* reply icmp packet (ICMP_MINLEN) followed by "sent packet" (ip + icmp headers) */
-        if (reply_buf_len < ICMP_MINLEN + sizeof(struct ip) + ICMP_MINLEN) {
-            /* discard ICMP message if we can't tell that it was caused by us (i.e. if the "sent packet" is not included). */
+        /* reply icmp packet (ICMPv6 header) followed by "sent packet" (IPv6 + ICMPv6 header) */
+        if (reply_buf_len < ICMP_MINLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) {
+            /* discard ICMPv6 message if we can't tell that it was caused by us (i.e. if the "sent packet" is not included). */
             return 0;
         }
 
-        sent_icmp = (struct icmp6_hdr *)(reply_buf + sizeof(struct icmp6_hdr) + sizeof(struct ip));
+        sent_ipv6 = (struct ip6_hdr *)(reply_buf + sizeof(struct icmp6_hdr));
+        if (sent_ipv6->ip6_nxt != IPPROTO_ICMPV6) {
+            /* discard ICMPv6 message if we can't tell that it was caused by
+             * us, because the IPv6 header is not directly followed by an
+             * ICMPv6 header
+             */
+            dbg_printf("invoking packet next header is %d\n", sent_ipv6->ip6_nxt);
+            return 0;
+        }
+        sent_icmp = (struct icmp6_hdr *)(reply_buf + sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr));
 
-        if (sent_icmp->icmp6_type != ICMP_ECHO || sent_icmp->icmp6_id != ident6) {
+        if (sent_icmp->icmp6_type != ICMP6_ECHO_REQUEST || sent_icmp->icmp6_id != ident6) {
             /* not caused by us */
             return 0;
         }
@@ -2897,39 +2948,56 @@ int decode_icmp_ipv6(
         }
 
         getnameinfo(response_addr, response_addr_len, addr_ascii, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
+        h = table[seqmap_value->host_nr];
 
         switch (icp->icmp6_type) {
-        case ICMP_UNREACH:
-            h = table[seqmap_value->host_nr];
-            if (icp->icmp6_code > ICMP_UNREACH_MAXTYPE) {
-                print_warning("ICMP Unreachable (Invalid Code) from %s for ICMP Echo sent to %s",
-                    addr_ascii, h->host);
-            }
-            else {
-                print_warning("%s from %s for ICMP Echo sent to %s",
-                    icmp_unreach_str[icp->icmp6_code], addr_ascii, h->host);
+        case ICMP6_DST_UNREACH:
+            if (icp->icmp6_code > ICMP6_UNREACH_MAXCODE) {
+                print_warning("ICMPv6 Destination Unreachable (Code %d) from %s for ICMPv6 Echo Request sent to %s",
+                    icp->icmp6_code, addr_ascii, h->host);
+            } else {
+                print_warning("ICMPv6 Destination Unreachable (%s) from %s for ICMPv6 Echo Request sent to %s",
+                    icmp6_unreach_str[icp->icmp6_code], addr_ascii, h->host);
             }
-
             print_warning("\n");
             num_othericmprcvd++;
             break;
 
-        case ICMP_SOURCEQUENCH:
-        case ICMP_REDIRECT:
-        case ICMP_TIMXCEED:
-        case ICMP_PARAMPROB:
-            h = table[seqmap_value->host_nr];
-            if (icp->icmp6_type <= ICMP_TYPE_STR_MAX) {
-                print_warning("%s from %s for ICMP Echo sent to %s",
-                    icmp_type_str[icp->icmp6_type], addr_ascii, h->host);
+        case ICMP6_PACKET_TOO_BIG:
+            print_warning("ICMPv6 Packet Too Big from %s for ICMPv6 Echo Request sent to %s\n",
+                addr_ascii, h->host);
+            num_othericmprcvd++;
+            break;
+
+        case ICMP6_TIME_EXCEEDED:
+            if (icp->icmp6_code > ICMP6_TIME_EXCEEDED_MAXCODE) {
+                print_warning("ICMPv6 Time Exceeded (Code %d) from %s for ICMPv6 Echo Request sent to %s",
+                    icp->icmp6_code, addr_ascii, h->host);
+            } else {
+                print_warning("ICMPv6 Time Exceeded (%s) from %s for ICMPv6 Echo Request sent to %s",
+                    icmp6_time_exceeded_str[icp->icmp6_code], addr_ascii, h->host);
             }
-            else {
-                print_warning("ICMP %d from %s for ICMP Echo sent to %s",
-                    icp->icmp6_type, addr_ascii, h->host);
+            print_warning("\n");
+            num_othericmprcvd++;
+            break;
+
+        case ICMP6_PARAM_PROB:
+            if (icp->icmp6_code > ICMP6_PARAM_PROB_MAXCODE) {
+                print_warning("ICMPv6 Parameter Problem (Code %d) from %s for ICMPv6 Echo Request sent to %s",
+                    icp->icmp6_code, addr_ascii, h->host);
+            } else {
+                print_warning("ICMPv6 Parameter Problem (%s) from %s for ICMPv6 Echo Request sent to %s",
+                    icmp6_param_prob_str[icp->icmp6_code], addr_ascii, h->host);
             }
             print_warning("\n");
             num_othericmprcvd++;
             break;
+
+        default:
+            print_warning("ICMPv6 Type %d Code %d from %s for ICMPv6 Echo Request sent to %s\n",
+                icp->icmp6_type, icp->icmp6_code, addr_ascii, h->host);
+            num_othericmprcvd++;
+            break;
         }
 
         return 0;
index 84a84eacf41c624c346247eae9f2d6daba004b32..15a96386e8021a2cf1febebe8be700a357bbfaff 100644 (file)
@@ -65,6 +65,20 @@ int open_ping_socket_ipv6(int *socktype)
         if (s < 0) {
             return -1;
         }
+    } else {
+        /* receive only ICMP6 messages relevant for fping on raw socket */
+        struct icmp6_filter recv_filter;
+
+        ICMP6_FILTER_SETBLOCKALL(&recv_filter);
+        ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &recv_filter);
+        ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &recv_filter);
+        ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &recv_filter);
+        ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &recv_filter);
+        ICMP6_FILTER_SETPASS(ICMP6_PARAM_PROB, &recv_filter);
+
+        if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &recv_filter, sizeof(recv_filter))) {
+            errno_crash_and_burn("cannot set icmp6 message type filter");
+        }
     }
 
     /* Make sure that we use non-blocking IO */