From: Guy Harris Date: Fri, 3 May 2019 03:01:08 +0000 (-0700) Subject: Handle the IPv6 Jumbo Payload option. X-Git-Tag: tcpdump-4.99-bp~787 X-Git-Url: https://git.tcpdump.org/tcpdump/commitdiff_plain/db7a7633e65ea66dacb40450d375c00d3f305408 Handle the IPv6 Jumbo Payload option. If we see one when processing the hop-by-hop extension header, use it to set the payload length. In UDP, if we have a zero length field in the UDP header, and the length of the data handed to us is > 65535, treat that as a Jumbo Payload packet. --- diff --git a/netdissect.c b/netdissect.c index 15594de8..4d4a4a59 100644 --- a/netdissect.c +++ b/netdissect.c @@ -193,6 +193,27 @@ nd_push_snapend(netdissect_options *ndo, const u_char *new_snapend) return (1); /* success */ } +/* + * Change an already-pushed snapshot end. This may increase the + * snapshot end, as it may be used, for example, for a Jumbo Payload + * option in IPv6. It must not increase it past the snapshot length + * atop which the current one was pushed, however. + */ +void +nd_change_snapend(netdissect_options *ndo, const u_char *new_snapend) +{ + struct netdissect_saved_packet_info *ndspi; + + ndspi = ndo->ndo_packet_info_stack; + if (ndspi->ndspi_prev != NULL) { + if (new_snapend <= ndspi->ndspi_prev->ndspi_snapend) + ndo->ndo_snapend = new_snapend; + } else { + if (new_snapend < ndo->ndo_snapend) + ndo->ndo_snapend = new_snapend; + } +} + void nd_pop_packet_info(netdissect_options *ndo) { diff --git a/netdissect.h b/netdissect.h index f2839e09..ca42421b 100644 --- a/netdissect.h +++ b/netdissect.h @@ -262,6 +262,7 @@ struct netdissect_options { extern int nd_push_buffer(netdissect_options *, u_char *, const u_char *, const u_char *); extern int nd_push_snapend(netdissect_options *, const u_char *); +extern void nd_change_snapend(netdissect_options *, const u_char *); extern void nd_pop_packet_info(netdissect_options *); extern void nd_pop_all_packet_info(netdissect_options *); @@ -585,7 +586,7 @@ extern void cnfp_print(netdissect_options *, const u_char *); extern void dccp_print(netdissect_options *, const u_char *, const u_char *, u_int); extern void decnet_print(netdissect_options *, const u_char *, u_int, u_int); extern void dhcp6_print(netdissect_options *, const u_char *, u_int); -extern int dstopt_print(netdissect_options *, const u_char *); +extern int dstopt_process(netdissect_options *, const u_char *); extern void dtp_print(netdissect_options *, const u_char *, u_int); extern void dvmrp_print(netdissect_options *, const u_char *, u_int); extern void eap_print(netdissect_options *, const u_char *, u_int); @@ -603,7 +604,7 @@ extern void ftp_print(netdissect_options *, const u_char *, u_int); extern void geneve_print(netdissect_options *, const u_char *, u_int); extern void geonet_print(netdissect_options *, const u_char *, u_int, const struct lladdr_info *); extern void gre_print(netdissect_options *, const u_char *, u_int); -extern int hbhopt_print(netdissect_options *, const u_char *); +extern int hbhopt_process(netdissect_options *, const u_char *, int *, uint32_t *); extern void hex_and_ascii_print(netdissect_options *, const char *, const u_char *, u_int); extern void hex_print(netdissect_options *, const char *ident, const u_char *cp, u_int); extern void hex_print_with_offset(netdissect_options *, const char *ident, const u_char *cp, u_int, u_int); diff --git a/print-ip6.c b/print-ip6.c index a9a18faf..2cc4e309 100644 --- a/print-ip6.c +++ b/print-ip6.c @@ -231,11 +231,14 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) const struct ip6_hdr *ip6; int advance; u_int len; + u_int total_advance; const u_char *cp; - u_int payload_len; + uint32_t payload_len; uint8_t nh; int fragmented = 0; u_int flow; + int found_extension_header; + int found_jumbo; ndo->ndo_protocol = "ip6"; ip6 = (const struct ip6_hdr *)bp; @@ -255,10 +258,35 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) } payload_len = GET_BE_U_2(ip6->ip6_plen); - len = payload_len + sizeof(struct ip6_hdr); - if (length < len) - ND_PRINT("truncated-ip6 - %u bytes missing!", - len - length); + /* + * RFC 1883 says: + * + * The Payload Length field in the IPv6 header must be set to zero + * in every packet that carries the Jumbo Payload option. If a + * packet is received with a valid Jumbo Payload option present and + * a non-zero IPv6 Payload Length field, an ICMP Parameter Problem + * message, Code 0, should be sent to the packet's source, pointing + * to the Option Type field of the Jumbo Payload option. + * + * Later versions of the IPv6 spec don't discuss the Jumbo Payload + * option. + * + * If the payload length is 0, we temporarily just set the total + * length to the remaining data in the packet (which, for Ethernet, + * could include frame padding, but if it's a Jumbo Payload frame, + * it shouldn't even be sendable over Ethernet, so we don't worry + * about that), so we can process the extension headers in order + * to *find* a Jumbo Payload hop-by-hop option and, when we've + * processed all the extension headers, check whether we found + * a Jumbo Payload option, and fail if we haven't. + */ + if (payload_len != 0) { + len = payload_len + sizeof(struct ip6_hdr); + if (length < len) + ND_PRINT("truncated-ip6 - %u bytes missing!", + len - length); + } else + len = length + sizeof(struct ip6_hdr); nh = GET_U_1(ip6->ip6_nxt); if (ndo->ndo_vflag) { @@ -292,12 +320,16 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) cp = (const u_char *)ip6; advance = sizeof(struct ip6_hdr); + total_advance = 0; /* Process extension headers */ + found_extension_header = 0; + found_jumbo = 0; while (cp < ndo->ndo_snapend && advance > 0) { if (len < (u_int)advance) goto trunc; cp += advance; len -= advance; + total_advance += advance; if (cp == (const u_char *)(ip6 + 1) && nh != IPPROTO_TCP && nh != IPPROTO_UDP && @@ -309,20 +341,22 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) switch (nh) { case IPPROTO_HOPOPTS: - advance = hbhopt_print(ndo, cp); + advance = hbhopt_process(ndo, cp, &found_jumbo, &payload_len); if (advance < 0) { nd_pop_packet_info(ndo); return; } + found_extension_header = 1; nh = GET_U_1(cp); break; case IPPROTO_DSTOPTS: - advance = dstopt_print(ndo, cp); + advance = dstopt_process(ndo, cp); if (advance < 0) { nd_pop_packet_info(ndo); return; } + found_extension_header = 1; nh = GET_U_1(cp); break; @@ -332,6 +366,7 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) nd_pop_packet_info(ndo); return; } + found_extension_header = 1; nh = GET_U_1(cp); fragmented = 1; break; @@ -351,6 +386,7 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) nd_pop_packet_info(ndo); return; } + found_extension_header = 1; nh = GET_U_1(cp); nd_pop_packet_info(ndo); return; @@ -362,6 +398,7 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) nd_pop_packet_info(ndo); return; } + found_extension_header = 1; nh = GET_U_1(cp); break; @@ -370,6 +407,62 @@ ip6_print(netdissect_options *ndo, const u_char *bp, u_int length) * Not an extension header; hand off to the * IP protocol demuxer. */ + if (found_jumbo) { + /* + * We saw a Jumbo Payload option. + * Set the length to the payload length + * plus the IPv6 header length, and + * change the snapshot length accordingly. + */ + len = payload_len + sizeof(struct ip6_hdr); + if (length < len) + ND_PRINT("truncated-ip6 - %u bytes missing!", + len - length); + nd_change_snapend(ndo, bp + len); + + /* + * Now subtract the length of the IPv6 + * header plus extension headers to get + * the payload length. + */ + len -= total_advance; + } else { + /* + * We didn't see a Jumbo Payload option; + * was the payload length zero? + */ + if (payload_len == 0) { + /* + * Yes. If we found an extension + * header, treat that as a truncated + * packet header, as there was + * no payload to contain an + * extension header. + */ + if (found_extension_header) + goto trunc; + + /* + * OK, we didn't see any extnesion + * header, but that means we have + * no payload, so set the length + * to the IPv6 header length, + * and change the snapshot length + * accordingly. + */ + len = sizeof(struct ip6_hdr); + nd_change_snapend(ndo, bp + len); + + /* + * Now subtract the length of + * the IPv6 header plus extension + * headers (there weren't any, so + * that's just the IPv6 header + * length) to get the payload length. + */ + len -= total_advance; + } + } ip_print_demux(ndo, cp, len, 6, fragmented, GET_U_1(ip6->ip6_hlim), nh, bp); nd_pop_packet_info(ndo); diff --git a/print-ip6opts.c b/print-ip6opts.c index acc36713..4039f44e 100644 --- a/print-ip6opts.c +++ b/print-ip6opts.c @@ -87,7 +87,8 @@ trunc: } static int -ip6_opt_print(netdissect_options *ndo, const u_char *bp, int len) +ip6_opt_process(netdissect_options *ndo, const u_char *bp, int len, + int *found_jumbo, uint32_t *jumbolen) { int i; int optlen = 0; @@ -108,14 +109,16 @@ ip6_opt_print(netdissect_options *ndo, const u_char *bp, int len) switch (GET_U_1(bp + i)) { case IP6OPT_PAD1: - ND_PRINT("(pad1)"); + if (ndo->ndo_vflag) + ND_PRINT("(pad1)"); break; case IP6OPT_PADN: if (len - i < IP6OPT_MINLEN) { ND_PRINT("(padn: trunc)"); goto trunc; } - ND_PRINT("(padn)"); + if (ndo->ndo_vflag) + ND_PRINT("(padn)"); break; case IP6OPT_ROUTER_ALERT: if (len - i < IP6OPT_RTALERT_LEN) { @@ -126,7 +129,8 @@ ip6_opt_print(netdissect_options *ndo, const u_char *bp, int len) ND_PRINT("(rtalert: invalid len %u)", GET_U_1(bp + i + 1)); goto trunc; } - ND_PRINT("(rtalert: 0x%04x) ", GET_BE_U_2(bp + i + 2)); + if (ndo->ndo_vflag) + ND_PRINT("(rtalert: 0x%04x) ", GET_BE_U_2(bp + i + 2)); break; case IP6OPT_JUMBO: if (len - i < IP6OPT_JUMBO_LEN) { @@ -137,7 +141,10 @@ ip6_opt_print(netdissect_options *ndo, const u_char *bp, int len) ND_PRINT("(jumbo: invalid len %u)", GET_U_1(bp + i + 1)); goto trunc; } - ND_PRINT("(jumbo: %u) ", GET_BE_U_4(bp + i + 2)); + *found_jumbo = 1; + *jumbolen = GET_BE_U_4(bp + i + 2); + if (ndo->ndo_vflag) + ND_PRINT("(jumbo: %u) ", *jumbolen); break; case IP6OPT_HOME_ADDRESS: if (len - i < IP6OPT_HOMEADDR_MINLEN) { @@ -148,25 +155,29 @@ ip6_opt_print(netdissect_options *ndo, const u_char *bp, int len) ND_PRINT("(homeaddr: invalid len %u)", GET_U_1(bp + i + 1)); goto trunc; } - ND_PRINT("(homeaddr: %s", ip6addr_string(ndo, bp + i + 2)); - if (GET_U_1(bp + i + 1) > IP6OPT_HOMEADDR_MINLEN - 2) { - if (ip6_sopt_print(ndo, bp + i + IP6OPT_HOMEADDR_MINLEN, - (optlen - IP6OPT_HOMEADDR_MINLEN)) == -1) + if (ndo->ndo_vflag) { + ND_PRINT("(homeaddr: %s", ip6addr_string(ndo, bp + i + 2)); + if (GET_U_1(bp + i + 1) > IP6OPT_HOMEADDR_MINLEN - 2) { + if (ip6_sopt_print(ndo, bp + i + IP6OPT_HOMEADDR_MINLEN, + (optlen - IP6OPT_HOMEADDR_MINLEN)) == -1) goto trunc; + } + ND_PRINT(")"); } - ND_PRINT(")"); break; default: if (len - i < IP6OPT_MINLEN) { ND_PRINT("(type %u: trunc)", GET_U_1(bp + i)); goto trunc; } - ND_PRINT("(opt_type 0x%02x: len=%u)", GET_U_1(bp + i), - GET_U_1(bp + i + 1)); + if (ndo->ndo_vflag) + ND_PRINT("(opt_type 0x%02x: len=%u)", GET_U_1(bp + i), + GET_U_1(bp + i + 1)); break; } } - ND_PRINT(" "); + if (ndo->ndo_vflag) + ND_PRINT(" "); return 0; trunc: @@ -174,7 +185,8 @@ trunc: } int -hbhopt_print(netdissect_options *ndo, const u_char *bp) +hbhopt_process(netdissect_options *ndo, const u_char *bp, int *found_jumbo, + uint32_t *jumbolen) { const struct ip6_hbh *dp = (const struct ip6_hbh *)bp; u_int hbhlen = 0; @@ -184,10 +196,9 @@ hbhopt_print(netdissect_options *ndo, const u_char *bp) hbhlen = (GET_U_1(dp->ip6h_len) + 1) << 3; ND_TCHECK_LEN(dp, hbhlen); ND_PRINT("HBH "); - if (ndo->ndo_vflag) - if (ip6_opt_print(ndo, (const u_char *)dp + sizeof(*dp), - hbhlen - sizeof(*dp)) == -1) - goto trunc; + if (ip6_opt_process(ndo, (const u_char *)dp + sizeof(*dp), + hbhlen - sizeof(*dp), found_jumbo, jumbolen) == -1) + goto trunc; return hbhlen; trunc: @@ -196,10 +207,12 @@ trunc: } int -dstopt_print(netdissect_options *ndo, const u_char *bp) +dstopt_process(netdissect_options *ndo, const u_char *bp) { const struct ip6_dest *dp = (const struct ip6_dest *)bp; u_int dstoptlen = 0; + int found_jumbo; + uint32_t jumbolen; ndo->ndo_protocol = "dstopt"; ND_TCHECK_1(dp->ip6d_len); @@ -207,8 +220,13 @@ dstopt_print(netdissect_options *ndo, const u_char *bp) ND_TCHECK_LEN(dp, dstoptlen); ND_PRINT("DSTOPT "); if (ndo->ndo_vflag) { - if (ip6_opt_print(ndo, (const u_char *)dp + sizeof(*dp), - dstoptlen - sizeof(*dp)) == -1) + /* + * The Jumbo Payload option is a hop-by-hop option; we print, + * but don't honor, Jumbo Payload destination options. + */ + if (ip6_opt_process(ndo, (const u_char *)dp + sizeof(*dp), + dstoptlen - sizeof(*dp), &found_jumbo, + &jumbolen) == -1) goto trunc; } diff --git a/print-udp.c b/print-udp.c index bf5c7ba5..77bf330e 100644 --- a/print-udp.c +++ b/print-udp.c @@ -389,7 +389,8 @@ udp_print(netdissect_options *ndo, const u_char *bp, u_int length, const struct ip *ip; const u_char *cp; const u_char *ep = ndo->ndo_snapend; - uint16_t sport, dport, ulen; + uint16_t sport, dport; + u_int ulen; const struct ip6_hdr *ip6; ndo->ndo_protocol = "udp"; @@ -417,6 +418,13 @@ udp_print(netdissect_options *ndo, const u_char *bp, u_int length, goto trunc; } ulen = GET_BE_U_2(up->uh_ulen); + /* + * IPv6 Jumbo Datagrams; see RFC 2675. + * If the length is zero, and the length provided to us is + * > 65535, use the provided length as the length. + */ + if (ulen == 0 && length > 65535) + ulen = length; if (ulen < sizeof(struct udphdr)) { udpipaddr_print(ndo, ip, sport, dport); ND_PRINT("truncated-udplength %u", ulen);