| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- // SPDX-License-Identifier: GPL-2.0
- #include <arpa/inet.h>
- #include <error.h>
- #include <linux/errqueue.h>
- #include <linux/icmp.h>
- #include <linux/icmpv6.h>
- #include <linux/in6.h>
- #include <linux/ip.h>
- #include <linux/ipv6.h>
- #include <netinet/in.h>
- #include <netinet/udp.h>
- #include <poll.h>
- #include <sched.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/ioctl.h>
- #include <sys/socket.h>
- #include "../kselftest_harness.h"
- static const unsigned short src_port = 44444;
- static const unsigned short dst_port = 55555;
- static const int min_orig_dgram_len = 128;
- static const int min_payload_len_v4 =
- min_orig_dgram_len - sizeof(struct iphdr) - sizeof(struct udphdr);
- static const int min_payload_len_v6 =
- min_orig_dgram_len - sizeof(struct ipv6hdr) - sizeof(struct udphdr);
- static const uint8_t orig_payload_byte = 0xAA;
- 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 int poll_err(int fd)
- {
- struct pollfd pfd;
- memset(&pfd, 0, sizeof(pfd));
- pfd.fd = fd;
- if (poll(&pfd, 1, 5000) != 1 || pfd.revents != POLLERR)
- return -1;
- return 0;
- }
- static void set_addr(struct sockaddr_inet *addr, int domain,
- unsigned short 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_and_setsockopt(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;
- 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;
- ASSERT_EQ(poll_err(sock), 0);
- recv = recvmsg(sock, &msg, MSG_ERRQUEUE);
- 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) {
- TH_LOG("Unrelated cmsgs were encountered in recvmsg()");
- continue;
- }
- 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_and_setsockopt(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
|