Skip to content

Commit bc8cf10

Browse files
committed
fix ICMPv6 error message handling and reporting
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.
1 parent fb6f8b5 commit bc8cf10

File tree

4 files changed

+120
-29
lines changed

4 files changed

+120
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Next
2121
- Options --print-tos and --print-ttl now also work for IPv6, and no
2222
longer require privileges (#384, thanks @auerswal)
2323

24+
- Report received ICMPv6 error messages (#391, thanks @auerswal)
25+
2426
fping 5.3 (2025-01-02)
2527
======================
2628

configure.ac

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ AC_ARG_ENABLE([ipv6],
3434
AS_HELP_STRING([--disable-ipv6], [Disable support for pinging IPv6 hosts]))
3535
AS_IF([test "x$enable_ipv6" != "xno"], [
3636
dnl Test if IPv6 is supported
37-
AC_CHECK_HEADERS([netinet/icmp6.h], [have_ipv6="yes"], [], [[
37+
AC_CHECK_HEADERS([netinet/icmp6.h], [have_ipv6="yes"], [have_ipv6="no"], [[
38+
#include <netinet/in.h>
39+
#include <sys/types.h>
40+
]])
41+
])
42+
AS_IF([test "x$have_ipv6" == "xyes"], [
43+
dnl Test if IPv6 is supported
44+
AC_CHECK_HEADERS([netinet/ip6.h], [have_ipv6="yes"], [have_ipv6="no"], [[
3845
#include <netinet/in.h>
3946
#include <sys/types.h>
4047
]])

src/fping.c

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ extern "C" {
7070

7171
#ifdef IPV6
7272
#include <netinet/icmp6.h>
73+
#include <netinet/ip6.h>
7374
#endif
7475
#include <netinet/in_systm.h>
7576

@@ -218,6 +219,46 @@ char *icmp_unreach_str[16] = {
218219

219220
#define ICMP_UNREACH_MAXTYPE 15
220221

222+
#ifdef IPV6
223+
/* Long names for ICMPv6 unreachable codes */
224+
#define ICMP6_UNREACH_MAXCODE 9
225+
char *icmp6_unreach_str[ICMP6_UNREACH_MAXCODE + 1] = {
226+
"No route to destination", /* 0 */
227+
"Communication with destination administratively prohibited", /* 1 */
228+
"Beyond scope of source address", /* 2 */
229+
"Address unreachable", /* 3 */
230+
"Port unreachable", /* 4 */
231+
"Source address failed ingress/egress policy", /* 5 */
232+
"Reject route to destination", /* 6 */
233+
"Error in Source Routing Header", /* 7 */
234+
"Headers too long", /* 8 */
235+
"Error in P-Route", /* 9 */
236+
};
237+
238+
/* Long names for ICMPv6 time exceeded codes */
239+
#define ICMP6_TIME_EXCEEDED_MAXCODE 1
240+
char *icmp6_time_exceeded_str[ICMP6_TIME_EXCEEDED_MAXCODE + 1] = {
241+
"Hop limit exceeded in transit", /* 0 */
242+
"Fragment reassembly time exceeded", /* 1 */
243+
};
244+
245+
/* Long names for ICMPv6 parameter problem codes */
246+
#define ICMP6_PARAM_PROB_MAXCODE 10
247+
char *icmp6_param_prob_str[ICMP6_PARAM_PROB_MAXCODE + 1] = {
248+
"Erroneous header field encountered", /* 0 */
249+
"Unrecognized Next Header type encountered", /* 1 */
250+
"Unrecognized IPv6 option encountered", /* 2 */
251+
"IPv6 First Fragment has incomplete IPv6 Header Chain", /* 3 */
252+
"SR Upper-layer Header Error", /* 4 */
253+
"Unrecognized Next Header type encountered by intermediate node", /* 5 */
254+
"Extension header too big", /* 6 */
255+
"Extension header chain too long", /* 7 */
256+
"Too many extension headers", /* 8 */
257+
"Too many options in extension header", /* 9 */
258+
"Option too big", /* 10 */
259+
};
260+
#endif
261+
221262
struct event;
222263
typedef struct host_entry {
223264
int i; /* index into array */
@@ -2620,29 +2661,39 @@ int decode_icmp_ipv6(
26202661
if (verbose_flag) {
26212662
char buf[INET6_ADDRSTRLEN];
26222663
getnameinfo(response_addr, response_addr_len, buf, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
2623-
printf("received packet too short for ICMP (%d bytes from %s)\n", (int)reply_buf_len, buf);
2664+
printf("received packet too short for ICMPv6 (%d bytes from %s)\n", (int)reply_buf_len, buf);
26242665
}
26252666
return 0; /* too short */
26262667
}
26272668

26282669
icp = (struct icmp6_hdr *)reply_buf;
26292670

26302671
if (icp->icmp6_type != ICMP6_ECHO_REPLY) {
2631-
/* Handle other ICMP packets */
2672+
/* Handle other ICMPv6 packets */
2673+
struct ip6_hdr *sent_ipv6;
26322674
struct icmp6_hdr *sent_icmp;
26332675
SEQMAP_VALUE *seqmap_value;
26342676
char addr_ascii[INET6_ADDRSTRLEN];
26352677
HOST_ENTRY *h;
26362678

2637-
/* reply icmp packet (ICMP_MINLEN) followed by "sent packet" (ip + icmp headers) */
2638-
if (reply_buf_len < ICMP_MINLEN + sizeof(struct ip) + ICMP_MINLEN) {
2639-
/* discard ICMP message if we can't tell that it was caused by us (i.e. if the "sent packet" is not included). */
2679+
/* reply icmp packet (ICMPv6 header) followed by "sent packet" (IPv6 + ICMPv6 header) */
2680+
if (reply_buf_len < ICMP_MINLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) {
2681+
/* discard ICMPv6 message if we can't tell that it was caused by us (i.e. if the "sent packet" is not included). */
26402682
return 0;
26412683
}
26422684

2643-
sent_icmp = (struct icmp6_hdr *)(reply_buf + sizeof(struct icmp6_hdr) + sizeof(struct ip));
2685+
sent_ipv6 = (struct ip6_hdr *)(reply_buf + sizeof(struct icmp6_hdr));
2686+
if (sent_ipv6->ip6_nxt != IPPROTO_ICMPV6) {
2687+
/* discard ICMPv6 message if we can't tell that it was caused by
2688+
* us, because the IPv6 header is not directly followed by an
2689+
* ICMPv6 header
2690+
*/
2691+
dbg_printf("invoking packet next header is %d\n", sent_ipv6->ip6_nxt);
2692+
return 0;
2693+
}
2694+
sent_icmp = (struct icmp6_hdr *)(reply_buf + sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr));
26442695

2645-
if (sent_icmp->icmp6_type != ICMP_ECHO || sent_icmp->icmp6_id != ident6) {
2696+
if (sent_icmp->icmp6_type != ICMP6_ECHO_REQUEST || sent_icmp->icmp6_id != ident6) {
26462697
/* not caused by us */
26472698
return 0;
26482699
}
@@ -2653,39 +2704,56 @@ int decode_icmp_ipv6(
26532704
}
26542705

26552706
getnameinfo(response_addr, response_addr_len, addr_ascii, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
2707+
h = table[seqmap_value->host_nr];
26562708

26572709
switch (icp->icmp6_type) {
2658-
case ICMP_UNREACH:
2659-
h = table[seqmap_value->host_nr];
2660-
if (icp->icmp6_code > ICMP_UNREACH_MAXTYPE) {
2661-
print_warning("ICMP Unreachable (Invalid Code) from %s for ICMP Echo sent to %s",
2662-
addr_ascii, h->host);
2663-
}
2664-
else {
2665-
print_warning("%s from %s for ICMP Echo sent to %s",
2666-
icmp_unreach_str[icp->icmp6_code], addr_ascii, h->host);
2710+
case ICMP6_DST_UNREACH:
2711+
if (icp->icmp6_code > ICMP6_UNREACH_MAXCODE) {
2712+
print_warning("ICMPv6 Destination Unreachable (Code %d) from %s for ICMPv6 Echo Request sent to %s",
2713+
icp->icmp6_code, addr_ascii, h->host);
2714+
} else {
2715+
print_warning("ICMPv6 Destination Unreachable (%s) from %s for ICMPv6 Echo Request sent to %s",
2716+
icmp6_unreach_str[icp->icmp6_code], addr_ascii, h->host);
26672717
}
2668-
26692718
print_warning("\n");
26702719
num_othericmprcvd++;
26712720
break;
26722721

2673-
case ICMP_SOURCEQUENCH:
2674-
case ICMP_REDIRECT:
2675-
case ICMP_TIMXCEED:
2676-
case ICMP_PARAMPROB:
2677-
h = table[seqmap_value->host_nr];
2678-
if (icp->icmp6_type <= ICMP_TYPE_STR_MAX) {
2679-
print_warning("%s from %s for ICMP Echo sent to %s",
2680-
icmp_type_str[icp->icmp6_type], addr_ascii, h->host);
2722+
case ICMP6_PACKET_TOO_BIG:
2723+
print_warning("ICMPv6 Packet Too Big from %s for ICMPv6 Echo Request sent to %s\n",
2724+
addr_ascii, h->host);
2725+
num_othericmprcvd++;
2726+
break;
2727+
2728+
case ICMP6_TIME_EXCEEDED:
2729+
if (icp->icmp6_code > ICMP6_TIME_EXCEEDED_MAXCODE) {
2730+
print_warning("ICMPv6 Time Exceeded (Code %d) from %s for ICMPv6 Echo Request sent to %s",
2731+
icp->icmp6_code, addr_ascii, h->host);
2732+
} else {
2733+
print_warning("ICMPv6 Time Exceeded (%s) from %s for ICMPv6 Echo Request sent to %s",
2734+
icmp6_time_exceeded_str[icp->icmp6_code], addr_ascii, h->host);
26812735
}
2682-
else {
2683-
print_warning("ICMP %d from %s for ICMP Echo sent to %s",
2684-
icp->icmp6_type, addr_ascii, h->host);
2736+
print_warning("\n");
2737+
num_othericmprcvd++;
2738+
break;
2739+
2740+
case ICMP6_PARAM_PROB:
2741+
if (icp->icmp6_code > ICMP6_PARAM_PROB_MAXCODE) {
2742+
print_warning("ICMPv6 Parameter Problem (Code %d) from %s for ICMPv6 Echo Request sent to %s",
2743+
icp->icmp6_code, addr_ascii, h->host);
2744+
} else {
2745+
print_warning("ICMPv6 Parameter Problem (%s) from %s for ICMPv6 Echo Request sent to %s",
2746+
icmp6_param_prob_str[icp->icmp6_code], addr_ascii, h->host);
26852747
}
26862748
print_warning("\n");
26872749
num_othericmprcvd++;
26882750
break;
2751+
2752+
default:
2753+
print_warning("ICMPv6 Type %d Code %d from %s for ICMPv6 Echo Request sent to %s\n",
2754+
icp->icmp6_type, icp->icmp6_code, addr_ascii, h->host);
2755+
num_othericmprcvd++;
2756+
break;
26892757
}
26902758

26912759
return 0;

src/socket6.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ int open_ping_socket_ipv6(int *socktype)
6565
if (s < 0) {
6666
return -1;
6767
}
68+
} else {
69+
/* receive only ICMP6 messages relevant for fping on raw socket */
70+
struct icmp6_filter recv_filter;
71+
72+
ICMP6_FILTER_SETBLOCKALL(&recv_filter);
73+
ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &recv_filter);
74+
ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &recv_filter);
75+
ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &recv_filter);
76+
ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED, &recv_filter);
77+
ICMP6_FILTER_SETPASS(ICMP6_PARAM_PROB, &recv_filter);
78+
79+
if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &recv_filter, sizeof(recv_filter))) {
80+
errno_crash_and_burn("cannot set icmp6 message type filter");
81+
}
6882
}
6983

7084
/* Make sure that we use non-blocking IO */

0 commit comments

Comments
 (0)