From 586e1e5b9b2ee8a69289984f069f46a8acc743dc Mon Sep 17 00:00:00 2001 From: Petr Vorel Date: Mon, 19 Aug 2024 16:58:47 +0200 Subject: [PATCH] ping: Lower max allowed -s value to 65507 (IPv4) or 65527 (IPv6) Maximum value for ping -s option cannot be higher than maximum IPv4/v6 packet size, which is 65535. More precisely, the value should respect maximum IPv4/v6 ICMP data length. Therefore limit: * ICMP payload: 65507 = 65535 (IPv4 packet size) - 20 (min IPv4 header size) - 8 (ICMP header size) * ICMPv6 payload: 65527 = 65535 (IPv6 packet size) - 8 (ICMPv6 header size) Use the higher value unless user chose the protocol. Linux kernel limits IPv4/6 packet size size exactly to these values: * ICMP datagram socket net/ipv4/ping.c in ping_common_sendmsg() (used in both ping_v4_sendmsg() and ping_v6_sendmsg()): if (len > 0xFFFF) return -EMSGSIZE; * raw socket IPv4 in raw_sendmsg() in net/ipv4/raw.c: err = -EMSGSIZE; if (len > 0xFFFF) goto out; * raw IPv6 socket is limited similarly in __ip6_append_data() in net/ipv6/ip6_output.c, where it more data could be passed if bigger MTU is respected (but for ping to be working also jumboheader is needed to be added): @@ -1473,7 +1473,7 @@ static int __ip6_append_data(struct sock *sk, } if (ip6_sk_ignore_df(sk)) - maxnonfragsize = sizeof(struct ipv6hdr) + IPV6_MAXPLEN; + maxnonfragsize = max_t(u32, mtu, sizeof(struct ipv6hdr) + IPV6_MAXPLEN); Because Big TCP support is limited only to TCP (ping uses UDP on both raw socket and ICMP datagram socket), it would have to be for IPv4 sent as multiple packets (IPv4 fragmentation), for IPv6 support IPv6 jumbograms (Hop-by-Hop option aka "Jumbo Payload option", see RFC 2675). Other ping implementations on Linux also limit it to similar values (Busybox: 65535, fping: 65507, inetutils: IPv4: 65399, IPv6: 65527, likely kernel limitation, FreeBSD limits on IPv4: 65507). Closes: https://github.com/iputils/iputils/pull/550 Signed-off-by: Petr Vorel --- doc/ping.xml | 5 ++++- ping/ping.c | 36 +++++++++++++++++++++++++++++------- ping/ping.h | 3 ++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/doc/ping.xml b/doc/ping.xml index 2ab0b850..962b6e05 100644 --- a/doc/ping.xml +++ b/doc/ping.xml @@ -646,7 +646,10 @@ xml:id="man.ping"> Specifies the number of data bytes to be sent. The default is 56, which translates into 64 ICMP data bytes when combined with the 8 bytes of ICMP header data. - Maximum allowed value is 127992, but though most systems + The maximum allowed value is 65507 for IPv4 + (65467 when or + or Intermediate hops) + or 65527 for IPv6, but most systems limit this to a smaller, system-dependent number. diff --git a/ping/ping.c b/ping/ping.c index d491be38..e19d8f02 100644 --- a/ping/ping.c +++ b/ping/ping.c @@ -59,6 +59,7 @@ #include #include #include +#include /* FIXME: global_rts will be removed in future */ struct ping_rts *global_rts; @@ -84,6 +85,12 @@ ping_func_set_st ping4_func_set = { #define NROUTES 9 /* number of record route slots */ #define TOS_MAX 255 /* 8-bit TOS field */ +/* max. IPv4 packet size - IPv4 header size - ICMP header size */ +#define ICMP_MAX_DATALEN (MAXPACKET - 20 - 8) + +/* max. IPv6 payload size - ICMPv6 Echo Reply Header */ +#define ICMPV6_MAX_DATALEN (MAXPACKET - sizeof (struct icmp6_hdr)) + #define CASE_TYPE(x) case x: return #x; static char *str_family(int family) @@ -345,6 +352,8 @@ main(int argc, char **argv) .ni.query = -1, .ni.subject_type = -1, }; + char buf[INET6_ADDRSTRLEN]; + /* FIXME: global_rts will be removed in future */ global_rts = &rts; @@ -535,7 +544,8 @@ main(int argc, char **argv) rts.opt_so_dontroute = 1; break; case 's': - rts.datalen = strtol_or_err(optarg, _("invalid argument"), 0, MAXPACKET - 8); + /* real validation is done later */ + rts.datalen = strtol_or_err(optarg, _("invalid argument"), 0, INT_MAX); break; case 'S': rts.sndbuf = strtol_or_err(optarg, _("invalid argument"), 1, INT_MAX); @@ -622,6 +632,24 @@ main(int argc, char **argv) hints.ai_family = AF_INET; } + int max_s = MAX(ICMP_MAX_DATALEN, ICMPV6_MAX_DATALEN); + + /* Detect based on -4 / -6 */ + if (hints.ai_family == AF_INET) + max_s = ICMP_MAX_DATALEN - get_ipv4_optlen(&rts); + else if (hints.ai_family == AF_INET6) + max_s = ICMPV6_MAX_DATALEN; + + /* Force limit on IPv4/IPv6 adresses */ + if (inet_pton(AF_INET, target, buf)) + max_s = ICMP_MAX_DATALEN - get_ipv4_optlen(&rts); + else if (inet_pton(AF_INET6, target, buf)) + max_s = ICMPV6_MAX_DATALEN; + + if (rts.datalen > max_s) + error(EXIT_FAILURE, 0, "invalid -s value: '%d': out of range: 0 <= value <= %d", + rts.datalen, max_s); + if (rts.opt_verbose) error(0, 0, "sock4.fd: %d (socktype: %s), sock6.fd: %d (socktype: %s)," " hints.ai_family: %s\n", @@ -643,7 +671,6 @@ main(int argc, char **argv) int target_ai_family = hints.ai_family; hints.ai_family = AF_UNSPEC; - char buf[INET6_ADDRSTRLEN]; if (!strchr(target, '%') && sock6.socktype == SOCK_DGRAM && inet_pton(AF_INET6, target, buf) > 0 && (IN6_IS_ADDR_LINKLOCAL(buf) || IN6_IS_ADDR_MC_LINKLOCAL(buf))) { @@ -1011,11 +1038,6 @@ int ping4_run(struct ping_rts *rts, int argc, char **argv, struct addrinfo *ai, error(2, errno, _("cannot set unicast time-to-live")); } - - if (rts->datalen > 0xFFFF - 8 - rts->optlen - 20) - error(2, 0, _("packet size %d is too large. Maximum is %d"), - rts->datalen, 0xFFFF - 8 - 20 - rts->optlen); - if (rts->datalen >= (int)sizeof(struct timeval)) /* can we time transfer */ rts->timing = 1; packlen = rts->datalen + MAXIPLEN + MAXICMPLEN; diff --git a/ping/ping.h b/ping/ping.h index d7d4f0b4..48e35b78 100644 --- a/ping/ping.h +++ b/ping/ping.h @@ -107,7 +107,8 @@ typedef uint32_t bitmap_t; # error Please MAX_DUP_CHK and/or BITMAP_SHIFT #endif -#define MAXPACKET 128000 /* max packet size */ +/* IPv4 packet size / IPv6 payload size */ +#define MAXPACKET 65535 struct rcvd_table { bitmap_t bitmap[MAX_DUP_CHK / (sizeof(bitmap_t) * 8)];