[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <willemdebruijn.kernel.1ad957d6f7314@gmail.com>
Date: Thu, 15 Jan 2026 09:51:28 -0500
From: Willem de Bruijn <willemdebruijn.kernel@...il.com>
To: Danielle Ratson <danieller@...dia.com>,
netdev@...r.kernel.org
Cc: linux-kselftest@...r.kernel.org,
linux-kernel@...r.kernel.org,
davem@...emloft.net,
edumazet@...gle.com,
kuba@...nel.org,
pabeni@...hat.com,
horms@...nel.org,
shuah@...nel.org,
mlxsw@...dia.com,
Danielle Ratson <danieller@...dia.com>
Subject: Re: [PATCH net-next] selftests: net: Add kernel selftest for RFC 4884
Danielle Ratson wrote:
> RFC 4884 extended certain ICMP messages with a length attribute that
> encodes the length of the "original datagram" field. This is needed so
> that new information could be appended to these messages without
> applications thinking that it is part of the "original datagram" field.
>
> In version 5.9, the kernel was extended with two new socket options
> (SOL_IP/IP_RECVERR_4884 and SOL_IPV6/IPV6_RECVERR_RFC4884) that allow
> user space to retrieve this length which is basically the offset to the
> ICMP Extension Structure at the end of the ICMP message. This is
> required by user space applications that need to parse the information
> contained in the ICMP Extension Structure. For example, the RFC 5837
> extension for tracepath.
>
> Add a selftest that verifies correct handling of the RFC 4884 length
> field for both IPv4 and IPv6, with and without extension structures,
> and validates that malformed extensions are correctly reported as invalid.
>
> For each address family, the test creates:
> - a raw socket used to send locally crafted ICMP error packets to the
> loopback address, and
> - a datagram socket used to receive the encapsulated original datagram
> and associated error metadata from the kernel error queue.
>
> ICMP packets are constructed entirely in user space rather than relying
> on kernel-generated errors. This allows the test to exercise invalid
> scenarios (such as corrupted checksums and incorrect length fields) and
> verify that the SO_EE_RFC4884_FLAG_INVALID flag is set as expected.
>
> Output Example:
>
> $ ./icmp_rfc4884
> Starting 18 tests from 18 test cases.
> RUN rfc4884.ipv4_ext_small_payload.rfc4884 ...
> OK rfc4884.ipv4_ext_small_payload.rfc4884
> ok 1 rfc4884.ipv4_ext_small_payload.rfc4884
> RUN rfc4884.ipv4_ext.rfc4884 ...
> OK rfc4884.ipv4_ext.rfc4884
> ok 2 rfc4884.ipv4_ext.rfc4884
> RUN rfc4884.ipv4_ext_large_payload.rfc4884 ...
> OK rfc4884.ipv4_ext_large_payload.rfc4884
> ok 3 rfc4884.ipv4_ext_large_payload.rfc4884
> RUN rfc4884.ipv4_no_ext_small_payload.rfc4884 ...
> OK rfc4884.ipv4_no_ext_small_payload.rfc4884
> ok 4 rfc4884.ipv4_no_ext_small_payload.rfc4884
> RUN rfc4884.ipv4_no_ext_min_payload.rfc4884 ...
> OK rfc4884.ipv4_no_ext_min_payload.rfc4884
> ok 5 rfc4884.ipv4_no_ext_min_payload.rfc4884
> RUN rfc4884.ipv4_no_ext_large_payload.rfc4884 ...
> OK rfc4884.ipv4_no_ext_large_payload.rfc4884
> ok 6 rfc4884.ipv4_no_ext_large_payload.rfc4884
> RUN rfc4884.ipv4_invalid_ext_checksum.rfc4884 ...
> OK rfc4884.ipv4_invalid_ext_checksum.rfc4884
> ok 7 rfc4884.ipv4_invalid_ext_checksum.rfc4884
> RUN rfc4884.ipv4_invalid_ext_length_small.rfc4884 ...
> OK rfc4884.ipv4_invalid_ext_length_small.rfc4884
> ok 8 rfc4884.ipv4_invalid_ext_length_small.rfc4884
> RUN rfc4884.ipv4_invalid_ext_length_large.rfc4884 ...
> OK rfc4884.ipv4_invalid_ext_length_large.rfc4884
> ok 9 rfc4884.ipv4_invalid_ext_length_large.rfc4884
> RUN rfc4884.ipv6_ext_small_payload.rfc4884 ...
> OK rfc4884.ipv6_ext_small_payload.rfc4884
> ok 10 rfc4884.ipv6_ext_small_payload.rfc4884
> RUN rfc4884.ipv6_ext.rfc4884 ...
> OK rfc4884.ipv6_ext.rfc4884
> ok 11 rfc4884.ipv6_ext.rfc4884
> RUN rfc4884.ipv6_ext_large_payload.rfc4884 ...
> OK rfc4884.ipv6_ext_large_payload.rfc4884
> ok 12 rfc4884.ipv6_ext_large_payload.rfc4884
> RUN rfc4884.ipv6_no_ext_small_payload.rfc4884 ...
> OK rfc4884.ipv6_no_ext_small_payload.rfc4884
> ok 13 rfc4884.ipv6_no_ext_small_payload.rfc4884
> RUN rfc4884.ipv6_no_ext_min_payload.rfc4884 ...
> OK rfc4884.ipv6_no_ext_min_payload.rfc4884
> ok 14 rfc4884.ipv6_no_ext_min_payload.rfc4884
> RUN rfc4884.ipv6_no_ext_large_payload.rfc4884 ...
> OK rfc4884.ipv6_no_ext_large_payload.rfc4884
> ok 15 rfc4884.ipv6_no_ext_large_payload.rfc4884
> RUN rfc4884.ipv6_invalid_ext_checksum.rfc4884 ...
> OK rfc4884.ipv6_invalid_ext_checksum.rfc4884
> ok 16 rfc4884.ipv6_invalid_ext_checksum.rfc4884
> RUN rfc4884.ipv6_invalid_ext_length_small.rfc4884 ...
> OK rfc4884.ipv6_invalid_ext_length_small.rfc4884
> ok 17 rfc4884.ipv6_invalid_ext_length_small.rfc4884
> RUN rfc4884.ipv6_invalid_ext_length_large.rfc4884 ...
> OK rfc4884.ipv6_invalid_ext_length_large.rfc4884
> ok 18 rfc4884.ipv6_invalid_ext_length_large.rfc4884
> PASSED: 18 / 18 tests passed.
> Totals: pass:18 fail:0 xfail:0 xpass:0 skip:0 error:0
>
> Signed-off-by: Danielle Ratson <danieller@...dia.com>
> Reviewed-by: Ido Schimmel <idosch@...dia.com>
> ---
> tools/testing/selftests/net/.gitignore | 1 +
> tools/testing/selftests/net/Makefile | 1 +
> tools/testing/selftests/net/icmp_rfc4884.c | 658 +++++++++++++++++++++
> 3 files changed, 660 insertions(+)
> create mode 100644 tools/testing/selftests/net/icmp_rfc4884.c
>
> diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
> index 6930fe926c58..97ad4d551d44 100644
> --- a/tools/testing/selftests/net/.gitignore
> +++ b/tools/testing/selftests/net/.gitignore
> @@ -7,6 +7,7 @@ cmsg_sender
> epoll_busy_poll
> fin_ack_lat
> hwtstamp_config
> +icmp_rfc4884
> io_uring_zerocopy_tx
> ioam6_parser
> ip_defrag
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index b66ba04f19d9..fe7937dc5f45 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -166,6 +166,7 @@ TEST_GEN_PROGS := \
> bind_timewait \
> bind_wildcard \
> epoll_busy_poll \
> + icmp_rfc4884 \
> ipv6_fragmentation \
> proc_net_pktgen \
> reuseaddr_conflict \
> diff --git a/tools/testing/selftests/net/icmp_rfc4884.c b/tools/testing/selftests/net/icmp_rfc4884.c
> new file mode 100644
> index 000000000000..043965289116
> --- /dev/null
> +++ b/tools/testing/selftests/net/icmp_rfc4884.c
> @@ -0,0 +1,658 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/icmp.h>
> +#include <linux/icmpv6.h>
> +#include <linux/in6.h>
> +#include <linux/ip.h>
> +#include <linux/ipv6.h>
> +#include <linux/errqueue.h>
no need to respin but tiny: alphabetical ordering
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <netinet/udp.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#define SRC_PORT 44444
> +#define DST_PORT 55555
> +#define MIN_ORIG_DGRAM_LEN 128
> +#define MIN_PAYLOAD_LEN_V4 \
> + (MIN_ORIG_DGRAM_LEN - sizeof(struct iphdr) - sizeof(struct udphdr))
> +#define MIN_PAYLOAD_LEN_V6 \
> + (MIN_ORIG_DGRAM_LEN - sizeof(struct ipv6hdr) - sizeof(struct udphdr))
> +#define ORIG_PAYLOAD_BYTE 0xAA
only if respinning: prefer typed (const) variables over macros
> +
> +struct sockaddr_inet {
> + union {
> + struct sockaddr_in6 v6;
> + struct sockaddr_in v4;
> + struct sockaddr sa;
> + };
> + socklen_t len;
> +};
> +
> +struct ip_case_info {
> + int domain;
> + int level;
> + int opt1;
> + int opt2;
> + int proto;
> + int (*build_func)(uint8_t *buf, ssize_t buflen, bool with_ext,
> + int payload_len, bool bad_csum, bool bad_len,
> + bool smaller_len);
> + int min_payload;
> +};
> +
> +static int bringup_loopback(void)
> +{
> + struct ifreq ifr = {
> + .ifr_name = "lo"
> + };
> + int fd;
> +
> + fd = socket(AF_INET, SOCK_DGRAM, 0);
> + if (fd < 0)
> + return -1;
> +
> + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
> + goto err;
> +
> + ifr.ifr_flags = ifr.ifr_flags | IFF_UP;
> +
> + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
> + goto err;
> +
> + close(fd);
> + return 0;
> +
> +err:
> + close(fd);
> + return -1;
> +}
> +
> +static uint16_t csum(const void *buf, size_t len)
> +{
> + const uint8_t *data = buf;
> + uint32_t sum = 0;
> +
> + while (len > 1) {
> + sum += (data[0] << 8) | data[1];
> + data += 2;
> + len -= 2;
> + }
> +
> + if (len == 1)
> + sum += data[0] << 8;
> +
> + while (sum >> 16)
> + sum = (sum & 0xFFFF) + (sum >> 16);
> +
> + return ~sum & 0xFFFF;
> +}
> +
> +static void set_addr(struct sockaddr_inet *addr, int domain, int port)
> +{
> + memset(addr, 0, sizeof(*addr));
> +
> + switch (domain) {
> + case AF_INET:
> + addr->v4.sin_family = AF_INET;
> + addr->v4.sin_port = htons(port);
> + addr->v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
> + addr->len = sizeof(addr->v4);
> + break;
> + case AF_INET6:
> + addr->v6.sin6_family = AF_INET6;
> + addr->v6.sin6_port = htons(port);
> + addr->v6.sin6_addr = in6addr_loopback;
> + addr->len = sizeof(addr->v6);
> + break;
> + }
> +}
> +
> +static int bind_to_loopback(int fd, const struct ip_case_info *info)
> +{
> + struct sockaddr_inet addr;
> + int opt = 1;
> +
> + set_addr(&addr, info->domain, SRC_PORT);
> +
> + if (setsockopt(fd, info->level, info->opt1, &opt, sizeof(opt)) < 0)
> + return -1;
> +
> + if (setsockopt(fd, info->level, info->opt2, &opt, sizeof(opt)) < 0)
> + return -1;
> +
no need to respin just for this but nit: this does more than the
function name covers. I was looking for where IPV6_RECVERR was set.
> + return bind(fd, &addr.sa, addr.len);
> +}
> +
> +static int build_rfc4884_ext(uint8_t *buf, size_t buflen, bool bad_csum,
> + bool bad_len, bool smaller_len)
> +{
> + struct icmp_extobj_hdr *objh;
> + struct icmp_ext_hdr *exthdr;
> + size_t obj_len, ext_len;
> + uint16_t sum;
> +
> + /* Use an object payload of 4 bytes */
> + obj_len = sizeof(*objh) + sizeof(uint32_t);
> + ext_len = sizeof(*exthdr) + obj_len;
> +
> + if (ext_len > buflen)
> + return -EINVAL;
> +
> + exthdr = (struct icmp_ext_hdr *)buf;
> + objh = (struct icmp_extobj_hdr *)(buf + sizeof(*exthdr));
> +
> + exthdr->version = 2;
> + /* When encoding a bad object length, either encode a length too small
> + * to fit the object header or too big to fit in the packet.
> + */
> + if (bad_len)
> + obj_len = smaller_len ? sizeof(*objh) - 1 : obj_len * 2;
> + objh->length = htons(obj_len);
> +
> + sum = csum(buf, ext_len);
> + exthdr->checksum = htons(bad_csum ? sum - 1 : sum);
> +
> + return ext_len;
> +}
> +
> +static int build_orig_dgram_v4(uint8_t *buf, ssize_t buflen, int payload_len)
> +{
> + struct udphdr *udph;
> + struct iphdr *iph;
> + size_t len = 0;
> +
> + len = sizeof(*iph) + sizeof(*udph) + payload_len;
> + if (len > buflen)
> + return -EINVAL;
> +
> + iph = (struct iphdr *)buf;
> + udph = (struct udphdr *)(buf + sizeof(*iph));
> +
> + iph->version = 4;
> + iph->ihl = 5;
> + iph->protocol = IPPROTO_UDP;
> + iph->saddr = htonl(INADDR_LOOPBACK);
> + iph->daddr = htonl(INADDR_LOOPBACK);
> + iph->tot_len = htons(len);
> + iph->check = htons(csum(iph, sizeof(*iph)));
> +
> + udph->source = htons(SRC_PORT);
> + udph->dest = htons(DST_PORT);
> + udph->len = htons(sizeof(*udph) + payload_len);
> +
> + memset(buf + sizeof(*iph) + sizeof(*udph), ORIG_PAYLOAD_BYTE,
> + payload_len);
> +
> + return len;
> +}
> +
> +static int build_orig_dgram_v6(uint8_t *buf, ssize_t buflen, int payload_len)
> +{
> + struct udphdr *udph;
> + struct ipv6hdr *iph;
> + size_t len = 0;
> +
> + len = sizeof(*iph) + sizeof(*udph) + payload_len;
> + if (len > buflen)
> + return -EINVAL;
> +
> + iph = (struct ipv6hdr *)buf;
> + udph = (struct udphdr *)(buf + sizeof(*iph));
> +
> + iph->version = 6;
> + iph->payload_len = htons(sizeof(*udph) + payload_len);
> + iph->nexthdr = IPPROTO_UDP;
> + iph->saddr = in6addr_loopback;
> + iph->daddr = in6addr_loopback;
> +
> + udph->source = htons(SRC_PORT);
> + udph->dest = htons(DST_PORT);
> + udph->len = htons(sizeof(*udph) + payload_len);
> +
> + memset(buf + sizeof(*iph) + sizeof(*udph), ORIG_PAYLOAD_BYTE,
> + payload_len);
> +
> + return len;
> +}
> +
> +static int build_icmpv4_pkt(uint8_t *buf, ssize_t buflen, bool with_ext,
> + int payload_len, bool bad_csum, bool bad_len,
> + bool smaller_len)
> +{
> + struct icmphdr *icmph;
> + int len, ret;
> +
> + len = sizeof(*icmph);
> + memset(buf, 0, buflen);
> +
> + icmph = (struct icmphdr *)buf;
> + icmph->type = ICMP_DEST_UNREACH;
> + icmph->code = ICMP_PORT_UNREACH;
> + icmph->checksum = 0;
> +
> + ret = build_orig_dgram_v4(buf + len, buflen - len, payload_len);
> + if (ret < 0)
> + return ret;
> +
> + len += ret;
> +
> + icmph->un.reserved[1] = (len - sizeof(*icmph)) / sizeof(uint32_t);
> +
> + if (with_ext) {
> + ret = build_rfc4884_ext(buf + len, buflen - len,
> + bad_csum, bad_len, smaller_len);
> + if (ret < 0)
> + return ret;
> +
> + len += ret;
> + }
> +
> + icmph->checksum = htons(csum(icmph, len));
> + return len;
> +}
> +
> +static int build_icmpv6_pkt(uint8_t *buf, ssize_t buflen, bool with_ext,
> + int payload_len, bool bad_csum, bool bad_len,
> + bool smaller_len)
> +{
> + struct icmp6hdr *icmph;
> + int len, ret;
> +
> + len = sizeof(*icmph);
> + memset(buf, 0, buflen);
> +
> + icmph = (struct icmp6hdr *)buf;
> + icmph->icmp6_type = ICMPV6_DEST_UNREACH;
> + icmph->icmp6_code = ICMPV6_PORT_UNREACH;
> + icmph->icmp6_cksum = 0;
> +
> + ret = build_orig_dgram_v6(buf + len, buflen - len, payload_len);
> + if (ret < 0)
> + return ret;
> +
> + len += ret;
> +
> + icmph->icmp6_datagram_len = (len - sizeof(*icmph)) / sizeof(uint64_t);
> +
> + if (with_ext) {
> + ret = build_rfc4884_ext(buf + len, buflen - len,
> + bad_csum, bad_len, smaller_len);
> + if (ret < 0)
> + return ret;
> +
> + len += ret;
> + }
> +
> + icmph->icmp6_cksum = htons(csum(icmph, len));
> + return len;
> +}
> +
> +FIXTURE(rfc4884) {};
> +
> +FIXTURE_SETUP(rfc4884)
> +{
> + int ret;
> +
> + ret = unshare(CLONE_NEWNET);
> + ASSERT_EQ(ret, 0) {
> + TH_LOG("unshare(CLONE_NEWNET) failed: %s", strerror(errno));
> + }
> +
> + ret = bringup_loopback();
> + ASSERT_EQ(ret, 0) TH_LOG("Failed to bring up loopback interface");
> +}
> +
> +FIXTURE_TEARDOWN(rfc4884)
> +{
> +}
> +
> +const struct ip_case_info ipv4_info = {
> + .domain = AF_INET,
> + .level = SOL_IP,
> + .opt1 = IP_RECVERR,
> + .opt2 = IP_RECVERR_RFC4884,
> + .proto = IPPROTO_ICMP,
> + .build_func = build_icmpv4_pkt,
> + .min_payload = MIN_PAYLOAD_LEN_V4,
> +};
> +
> +const struct ip_case_info ipv6_info = {
> + .domain = AF_INET6,
> + .level = SOL_IPV6,
> + .opt1 = IPV6_RECVERR,
> + .opt2 = IPV6_RECVERR_RFC4884,
> + .proto = IPPROTO_ICMPV6,
> + .build_func = build_icmpv6_pkt,
> + .min_payload = MIN_PAYLOAD_LEN_V6,
> +};
> +
> +FIXTURE_VARIANT(rfc4884) {
> + /* IPv4/v6 related information */
> + struct ip_case_info info;
> + /* Whether to append an ICMP extension or not */
> + bool with_ext;
> + /* UDP payload length */
> + int payload_len;
> + /* Whether to generate a bad checksum in the ICMP extension structure */
> + bool bad_csum;
> + /* Whether to generate a bad length in the ICMP object header */
> + bool bad_len;
> + /* Whether it is too small to fit the object header or too big to fit
> + * in the packet
> + */
> + bool smaller_len;
> +};
> +
> +/* Tests that a valid ICMPv4 error message with extension and the original
> + * datagram is smaller than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_ext_small_payload) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = 64,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv4 error message with extension and 128 bytes original
> + * datagram, generates an error with the expected offset, and does not raise the
> + * SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_ext) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V4,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv4 error message with extension and the original
> + * datagram is larger than 128 bytes, generates an error with the expected
> + * offset, and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_ext_large_payload) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = 256,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv4 error message without extension and the original
> + * datagram is smaller than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_no_ext_small_payload) {
> + .info = ipv4_info,
> + .with_ext = false,
> + .payload_len = 64,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv4 error message without extension and 128 bytes
> + * original datagram, generates an error with zero offset, and does not raise
> + * the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_no_ext_min_payload) {
> + .info = ipv4_info,
> + .with_ext = false,
> + .payload_len = MIN_PAYLOAD_LEN_V4,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv4 error message without extension and the original
> + * datagram is larger than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_no_ext_large_payload) {
> + .info = ipv4_info,
> + .with_ext = false,
> + .payload_len = 256,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that an ICMPv4 error message with extension and an invalid checksum,
> + * generates an error with the expected offset, and raises the
> + * SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_invalid_ext_checksum) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V4,
> + .bad_csum = true,
> + .bad_len = false,
> +};
> +
> +/* Tests that an ICMPv4 error message with extension and an object length
> + * smaller than the object header, generates an error with the expected offset,
> + * and raises the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_invalid_ext_length_small) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V4,
> + .bad_csum = false,
> + .bad_len = true,
> + .smaller_len = true,
> +};
> +
> +/* Tests that an ICMPv4 error message with extension and an object length that
> + * is too big to fit in the packet, generates an error with the expected offset,
> + * and raises the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv4_invalid_ext_length_large) {
> + .info = ipv4_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V4,
> + .bad_csum = false,
> + .bad_len = true,
> + .smaller_len = false,
> +};
> +
> +/* Tests that a valid ICMPv6 error message with extension and the original
> + * datagram is smaller than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_ext_small_payload) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = 64,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv6 error message with extension and 128 bytes original
> + * datagram, generates an error with the expected offset, and does not raise the
> + * SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_ext) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V6,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv6 error message with extension and the original
> + * datagram is larger than 128 bytes, generates an error with the expected
> + * offset, and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_ext_large_payload) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = 256,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +/* Tests that a valid ICMPv6 error message without extension and the original
> + * datagram is smaller than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_no_ext_small_payload) {
> + .info = ipv6_info,
> + .with_ext = false,
> + .payload_len = 64,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv6 error message without extension and 128 bytes
> + * original datagram, generates an error with zero offset, and does not
> + * raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_no_ext_min_payload) {
> + .info = ipv6_info,
> + .with_ext = false,
> + .payload_len = MIN_PAYLOAD_LEN_V6,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that a valid ICMPv6 error message without extension and the original
> + * datagram is larger than 128 bytes, generates an error with zero offset,
> + * and does not raise the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_no_ext_large_payload) {
> + .info = ipv6_info,
> + .with_ext = false,
> + .payload_len = 256,
> + .bad_csum = false,
> + .bad_len = false,
> +};
> +
> +/* Tests that an ICMPv6 error message with extension and an invalid checksum,
> + * generates an error with the expected offset, and raises the
> + * SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_invalid_ext_checksum) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V6,
> + .bad_csum = true,
> + .bad_len = false,
> +};
> +
> +/* Tests that an ICMPv6 error message with extension and an object length
> + * smaller than the object header, generates an error with the expected offset,
> + * and raises the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_invalid_ext_length_small) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V6,
> + .bad_csum = false,
> + .bad_len = true,
> + .smaller_len = true,
> +};
> +
> +/* Tests that an ICMPv6 error message with extension and an object length that
> + * is too big to fit in the packet, generates an error with the expected offset,
> + * and raises the SO_EE_RFC4884_FLAG_INVALID flag.
> + */
> +FIXTURE_VARIANT_ADD(rfc4884, ipv6_invalid_ext_length_large) {
> + .info = ipv6_info,
> + .with_ext = true,
> + .payload_len = MIN_PAYLOAD_LEN_V6,
> + .bad_csum = false,
> + .bad_len = true,
> + .smaller_len = false,
> +};
> +
> +static void
> +check_rfc4884_offset(struct __test_metadata *_metadata, int sock,
> + const FIXTURE_VARIANT(rfc4884) *v)
> +{
> + char rxbuf[1024];
> + char ctrl[1024];
> + struct iovec iov = {
> + .iov_base = rxbuf,
> + .iov_len = sizeof(rxbuf)
> + };
> + struct msghdr msg = {
> + .msg_iov = &iov,
> + .msg_iovlen = 1,
> + .msg_control = ctrl,
> + .msg_controllen = sizeof(ctrl),
> + };
> + struct cmsghdr *cmsg;
> + int recv;
> +
> + recv = recvmsg(sock, &msg, MSG_ERRQUEUE);
Reading from the error queue does not block.
Is it assured that the ICMP packet is queued on return from sendmsg?
Or does this need a poll to be on the safe side wrt flakes.
> + ASSERT_GE(recv, 0) TH_LOG("recvmsg(MSG_ERRQUEUE) failed");
> +
> + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
> + bool is_invalid, expected_invalid;
> + struct sock_extended_err *ee;
> + int expected_off;
> + uint16_t off;
> +
> + if (cmsg->cmsg_level != v->info.level &&
> + cmsg->cmsg_type != v->info.opt1)
> + continue;
Do we expect any other cmsg? Else just fail if encountering them, to
make the test more strict?
> +
> + ee = (struct sock_extended_err *)CMSG_DATA(cmsg);
> + off = ee->ee_rfc4884.len;
> + is_invalid = ee->ee_rfc4884.flags & SO_EE_RFC4884_FLAG_INVALID;
> +
> + expected_invalid = v->bad_csum || v->bad_len;
> + ASSERT_EQ(is_invalid, expected_invalid) {
> + TH_LOG("Expected invalidity flag to be %d, but got %d",
> + expected_invalid, is_invalid);
> + }
> +
> + expected_off =
> + (v->with_ext && v->payload_len >= v->info.min_payload) ?
> + v->payload_len : 0;
> + ASSERT_EQ(off, expected_off) {
> + TH_LOG("Expected RFC4884 offset %u, got %u",
> + expected_off, off);
> + }
> + break;
> + }
> +}
> +
> +TEST_F(rfc4884, rfc4884)
> +{
> + const typeof(variant) v = variant;
> + struct sockaddr_inet addr;
> + uint8_t pkt[1024];
> + int dgram, raw;
> + int len, sent;
> + int err;
> +
> + dgram = socket(v->info.domain, SOCK_DGRAM, 0);
> + ASSERT_GE(dgram, 0) TH_LOG("Opening datagram socket failed");
> +
> + err = bind_to_loopback(dgram, &v->info);
> + ASSERT_EQ(err, 0) TH_LOG("Bind failed");
> +
> + raw = socket(v->info.domain, SOCK_RAW, v->info.proto);
> + ASSERT_GE(raw, 0) TH_LOG("Opening raw socket failed");
> +
> + len = v->info.build_func(pkt, sizeof(pkt), v->with_ext, v->payload_len,
> + v->bad_csum, v->bad_len, v->smaller_len);
> + ASSERT_GT(len, 0) TH_LOG("Building packet failed");
> +
> + set_addr(&addr, v->info.domain, 0);
> + sent = sendto(raw, pkt, len, 0, &addr.sa, addr.len);
> + ASSERT_EQ(len, sent) TH_LOG("Sending packet failed");
> +
> + check_rfc4884_offset(_metadata, dgram, v);
> +
> + close(dgram);
> + close(raw);
> +}
> +
> +TEST_HARNESS_MAIN
> --
> 2.51.0
>
Powered by blists - more mailing lists