Skip to content

Commit

Permalink
ping: Lower max allowed -s value to 65507 (IPv4) or 65527 (IPv6)
Browse files Browse the repository at this point in the history
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 choose the protocol (-4/-6 options or
using ping4 or ping6 symlinks) or target is a numeric address.

Forcing a correct size workaround a slowdown on exit when using -s
<65535-65528> with -c fixed by 0ee3d47 ("ping: Fix EMSGSIZE on -s >
65527 on ICMP datagram socket"), but it's better to keep that fix
in case this gets reverted.

Research:

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: iputils#550
Fixes: 87dbb3a
Reviewed-by: Cyril Hrubis <[email protected]>
Reviewed-by: Benjamin Poirier <[email protected]>
Signed-off-by: Petr Vorel <[email protected]>
  • Loading branch information
pevik committed Sep 2, 2024
1 parent 65f102d commit 1e24c5b
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 9 deletions.
5 changes: 4 additions & 1 deletion doc/ping.xml
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,10 @@ xml:id="man.ping">
<para>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 <option>-R</option> or <option>-T</option>
or Intermediate <emphasis remap="I">hop</emphasis>s)
or 65527 for IPv6, but most systems
limit this to a smaller, system-dependent number.</para>
</listitem>
</varlistentry>
Expand Down
36 changes: 29 additions & 7 deletions ping/ping.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
#include <ifaddrs.h>
#include <math.h>
#include <locale.h>
#include <sys/param.h>

/* FIXME: global_rts will be removed in future */
struct ping_rts *global_rts;
Expand All @@ -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)
Expand Down Expand Up @@ -345,6 +352,8 @@ main(int argc, char **argv)
.ni.query = -1,
.ni.subject_type = -1,
};
unsigned char buf[sizeof(struct in6_addr)];

/* FIXME: global_rts will be removed in future */
global_rts = &rts;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -643,7 +671,6 @@ main(int argc, char **argv)
int target_ai_family = hints.ai_family;
hints.ai_family = AF_UNSPEC;

unsigned char buf[sizeof(struct in6_addr)];
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))) {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion ping/ping.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
Expand Down

0 comments on commit 1e24c5b

Please sign in to comment.