diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index a802cb5e86..56306fbf53 100644 --- a/src/libsystemd-network/dhcp-client-send.c +++ b/src/libsystemd-network/dhcp-client-send.c @@ -98,7 +98,7 @@ int dhcp_client_send_raw( fd_close = fd; } - dhcp_packet_append_ip_headers( + r = dhcp_packet_append_ip_headers( packet, INADDR_ANY, client->port, @@ -106,6 +106,8 @@ int dhcp_client_send_raw( client->server_port, sizeof(DHCPPacket) + optoffset, client->ip_service_type); + if (r < 0) + return r; r = dhcp_network_send_raw_socket( fd, diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 90eae88379..27b09a25bb 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -4,11 +4,12 @@ ***/ #include -#include #include "dhcp-option.h" #include "dhcp-packet.h" -#include "log.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" #include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -79,133 +80,42 @@ int dhcp_message_init( return 0; } -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { - uint64_t *buf_64 = (uint64_t*)buf; - uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); - uint64_t sum = 0; +int dhcp_packet_append_ip_headers( + DHCPPacket *packet, + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + uint16_t len, + int ip_service_type) { - /* See RFC1071 */ + struct iphdr ip; + struct udphdr udp; + int r; - while (buf_64 < end_64) { - sum += *buf_64; - if (sum < *buf_64) - /* wrap around in one's complement */ - sum++; + assert(packet); + assert(len > offsetof(DHCPPacket, dhcp)); - buf_64++; - } + r = udp_packet_build( + source_addr, + source_port, + destination_addr, + destination_port, + ip_service_type, + &(struct iovec_wrapper) { + .iovec = &IOVEC_MAKE(&packet->dhcp, len - offsetof(DHCPPacket, dhcp)), + .count = 1, + }, + &ip, + &udp); + if (r < 0) + return r; - if (len % sizeof(uint64_t)) { - /* If the buffer is not aligned to 64-bit, we need - to zero-pad the last few bytes and add them in */ - uint64_t buf_tail = 0; - - memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); - - sum += buf_tail; - if (sum < buf_tail) - /* wrap around */ - sum++; - } - - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return ~sum; -} - -void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, - uint16_t source_port, be32_t destination_addr, - uint16_t destination_port, uint16_t len, int ip_service_type) { - packet->ip.version = IPVERSION; - packet->ip.ihl = DHCP_IP_SIZE / 4; - packet->ip.tot_len = htobe16(len); - - if (ip_service_type >= 0) - packet->ip.tos = ip_service_type; - else - packet->ip.tos = IPTOS_CLASS_CS6; - - packet->ip.protocol = IPPROTO_UDP; - packet->ip.saddr = source_addr; - packet->ip.daddr = destination_addr; - - packet->udp.source = htobe16(source_port); - packet->udp.dest = htobe16(destination_port); - - packet->udp.len = htobe16(len - DHCP_IP_SIZE); - - packet->ip.check = packet->udp.len; - packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8); - - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); + packet->ip = ip; + packet->udp = udp; + return 0; } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { - size_t hdrlen; - - assert(packet); - - if (len < sizeof(DHCPPacket)) - return 0; - - /* IP */ - - if (packet->ip.version != IPVERSION) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not IPv4"); - - if (packet->ip.ihl < 5) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%i words) invalid", - packet->ip.ihl); - - hdrlen = packet->ip.ihl * 4; - if (hdrlen < 20) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: IPv4 IHL (%zu bytes) smaller than minimum (20 bytes)", - hdrlen); - - if (len < hdrlen) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by IP header", - len, hdrlen); - - /* UDP */ - - if (packet->ip.protocol != IPPROTO_UDP) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: not UDP"); - - if (len < hdrlen + be16toh(packet->udp.len)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: packet (%zu bytes) smaller than expected (%zu) by UDP header", - len, hdrlen + be16toh(packet->udp.len)); - - if (be16toh(packet->udp.dest) != port) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: to port %u, which is not the DHCP client port (%u)", - be16toh(packet->udp.dest), port); - - /* checksums - computing these is relatively expensive, so only do it - if all the other checks have passed - */ - - if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid IP checksum"); - - if (checksum && packet->udp.check) { - packet->ip.check = packet->udp.len; - packet->ip.ttl = 0; - - if (dhcp_packet_checksum(&packet->ip.ttl, - be16toh(packet->udp.len) + 12)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "ignoring packet: invalid UDP checksum"); - } - - return 0; + return udp_packet_verify(&IOVEC_MAKE(packet, len), port, checksum, /* ret_payload= */ NULL); } diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 967bd5d89d..90ea2caac9 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,9 +23,7 @@ int dhcp_message_init( size_t optlen, size_t *ret_optoffset); -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); - -void dhcp_packet_append_ip_headers( +int dhcp_packet_append_ip_headers( DHCPPacket *packet, be32_t source_addr, uint16_t source, diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c new file mode 100644 index 0000000000..3f062a7ef0 --- /dev/null +++ b/src/libsystemd-network/ip-util.c @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "log.h" + +union iphdr_union { + struct iphdr ip; + uint8_t buf[15 * 4]; /* ip->ihl is 4 bits, hence max length is 15 * 4 */ +}; + +struct udp_pseudo_header { + be32_t saddr; + be32_t daddr; + uint8_t unused; + uint8_t protocol; + be16_t len; +} _packed_; + +static uint64_t complement_sum(uint64_t a, uint64_t b) { + /* This performs one's complement addition (end-around carry). See RFC1071. */ + if (a <= UINT64_MAX - b) + return a + b; + + return a - (UINT64_MAX - b); +} + +static uint64_t checksum_iov(uint64_t sum, const struct iovec *iov) { + assert(iov); + + for (struct iovec i = *iov; iovec_is_set(&i); iovec_inc(&i, sizeof(uint64_t))) { + uint64_t t = 0; + memcpy(&t, i.iov_base, MIN(i.iov_len, sizeof(uint64_t))); + sum = complement_sum(sum, t); + } + + return sum; +} + +static uint16_t checksum_finalize(uint64_t sum) { + while ((sum >> 16) != 0) + sum = (sum & 0xffffu) + (sum >> 16); + + return ~sum; +} + +uint16_t ip_checksum(const void *buf, size_t len) { + /* See RFC1071 */ + return checksum_finalize(checksum_iov(0, &IOVEC_MAKE(buf, len))); +} + +static uint16_t iphdr_checksum(const union iphdr_union *ip) { + assert(ip); + return ip_checksum(ip, ip->ip.ihl * 4); +} + +static uint16_t udphdr_checksum( + be32_t saddr, + be32_t daddr, + const struct udphdr *udp, + const struct iovec_wrapper *payload) { + + assert(udp); + assert(payload); + + /* RFC 768 */ + + struct udp_pseudo_header pseudo = { + .saddr = saddr, + .daddr = daddr, + .protocol = IPPROTO_UDP, + .len = udp->len, + }; + + uint64_t sum = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(&pseudo, sizeof(struct udp_pseudo_header))); + sum = checksum_iov(sum, &IOVEC_MAKE(udp, sizeof(struct udphdr))); + + uint8_t buf[2] = {}; + bool odd = false; + FOREACH_ARRAY(i, payload->iovec, payload->count) { + if (!iovec_is_set(i)) + continue; + + struct iovec v = *i; + if (odd) { + buf[1] = *(uint8_t*) v.iov_base; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + iovec_inc(&v, 1); + } + + odd = v.iov_len % 2; + if (odd) { + buf[0] = ((uint8_t*) v.iov_base)[v.iov_len - 1]; + v.iov_len--; + } + sum = checksum_iov(sum, &v); + } + if (odd) { + buf[1] = 0; + sum = checksum_iov(sum, &IOVEC_MAKE(buf, 2)); + } + + return checksum_finalize(sum); +} + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr) { + + assert(payload); + assert(ret_iphdr); + assert(ret_udphdr); + + /* When ip_service_type is negative, IPTOS_CLASS_CS6 will be used. Otherwise, it must be a valid TOS, + * hence must be in 0…255. Here, we only check its range. */ + if (ip_service_type > UINT8_MAX) + return -EINVAL; + + /* iphdr.tot_len is uint16_t, hence the total length must be <= UINT16_MAX. */ + size_t len = iovw_size(payload); + if (len > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + union iphdr_union ip = { + .ip.version = IPVERSION, + .ip.ihl = sizeof(struct iphdr) / 4, + .ip.tos = ip_service_type >= 0 ? ip_service_type : IPTOS_CLASS_CS6, + .ip.tot_len = htobe16(sizeof(struct iphdr) + sizeof(struct udphdr) + len), + .ip.ttl = IPDEFTTL, + .ip.protocol = IPPROTO_UDP, + .ip.saddr = source_addr, + .ip.daddr = destination_addr, + }; + + ip.ip.check = iphdr_checksum(&ip); + + struct udphdr udp = { + .source = htobe16(source_port), + .dest = htobe16(destination_port), + .len = htobe16(sizeof(struct udphdr) + len), + }; + + udp.check = udphdr_checksum(source_addr, destination_addr, &udp, payload); + + *ret_iphdr = ip.ip; + *ret_udphdr = udp; + return 0; +} + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload) { + + assert(packet); + + /* This verifies IP and UDP packet headers and optionally returns the UDP payload. */ + + /* IP */ + if (packet->iov_len < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than minimum IP header (%zu bytes), ignoring packet.", + packet->iov_len, sizeof(struct iphdr)); + + const union iphdr_union *ip = (const union iphdr_union*) packet->iov_base; + if (ip->ip.version != IPVERSION) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet is not IPv4, ignoring packet."); + + size_t iphdrlen = ip->ip.ihl * 4; + if (iphdrlen < sizeof(struct iphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: IP header size (%zu bytes) smaller than minimum (%zu bytes), ignoring packet.", + iphdrlen, sizeof(struct iphdr)); + + if (packet->iov_len < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than IP header size (%zu bytes), ignoring packet.", + packet->iov_len, iphdrlen); + + size_t totlen = be16toh(ip->ip.tot_len); + if (totlen < iphdrlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet size (%zu bytes) by IP header is smaller than the IP header size (%zu), ignoring packet.", + totlen, iphdrlen); + if (packet->iov_len < totlen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: packet (%zu bytes) smaller than expected (%zu) by IP header, ignoring packet.", + packet->iov_len, totlen); + + if (ip->ip.protocol != IPPROTO_UDP) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: not UDP, ignoring packet."); + + if (iphdr_checksum(ip) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "IPv4: invalid IP checksum, ignoring packet."); + + /* UDP */ + if (totlen < iphdrlen + sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet (%zu bytes) smaller than IP header + UDP header, ignoring packet.", + totlen); + + const struct udphdr *udp = (const struct udphdr*) ((const uint8_t*) packet->iov_base + iphdrlen); + size_t udplen = be16toh(udp->len); + if (udplen < sizeof(struct udphdr)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: UDP datagram (%zu bytes) smaller than UDP header (%zu bytes), ignoring packet.", + udplen, sizeof(struct udphdr)); + + if (totlen != iphdrlen + udplen) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: packet length by IP header (%zu bytes) does not match with the one by UDP header " + "(IP header %zu bytes + UDP %zu bytes = %zu bytes), ignoring packet.", + totlen, iphdrlen, udplen, iphdrlen + udplen); + + if (be16toh(udp->dest) != port) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: to port %u, which is not the expected port (%u), ignoring packet.", + be16toh(udp->dest), port); + + /* Calculate the UDP payload length from the UDP header (udplen), rather than the input packet length + * (len). The packet may contain garbage at the end. */ + struct iovec payload = IOVEC_MAKE( + (const uint8_t*) packet->iov_base + iphdrlen + sizeof(struct udphdr), + udplen - sizeof(struct udphdr)); + if (checksum && udp->check != 0 && + udphdr_checksum(ip->ip.saddr, ip->ip.daddr, udp, + &(struct iovec_wrapper) { + .iovec = &payload, + .count = 1, + }) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "UDP: invalid UDP checksum, ignoring packet."); + + if (ret_payload) + *ret_payload = payload; + return 0; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h new file mode 100644 index 0000000000..31fa4631c3 --- /dev/null +++ b/src/libsystemd-network/ip-util.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-forward.h" + +#include "sparse-endian.h" + +/* RFC 791 + * Fragmentation and Reassembly. + * Every internet destination must be able to receive a datagram of 576 octets either in one piece or in + * fragments to be reassembled. */ +#define IPV4_MIN_REASSEMBLY_SIZE 576u + +/* This is a maximal UDP payload size in a packet when its IP header does not contain options. When a packet + * contains some IP options, then of course the allowed UDP payload size in the packet becomes smaller. */ +#define UDP_PAYLOAD_MAX_SIZE (UINT16_MAX - sizeof(struct iphdr) - sizeof(struct udphdr)) + +uint16_t ip_checksum(const void *buf, size_t len); + +int udp_packet_build( + be32_t source_addr, + uint16_t source_port, + be32_t destination_addr, + uint16_t destination_port, + int ip_service_type, + const struct iovec_wrapper *payload, + struct iphdr *ret_iphdr, + struct udphdr *ret_udphdr); + +int udp_packet_verify( + const struct iovec *packet, + uint16_t port, + bool checksum, + struct iovec *ret_payload); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index d1e13d99b5..b0443c3695 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -12,6 +12,7 @@ libsystemd_network_sources = files( 'dhcp6-protocol.c', 'icmp6-packet.c', 'icmp6-util.c', + 'ip-util.c', 'lldp-neighbor.c', 'lldp-network.c', 'ndisc-option.c', @@ -84,6 +85,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp6-client.c'), }, + network_test_template + { + 'sources' : files('test-ip-util.c'), + }, network_test_template + { 'sources' : files('test-ipv4ll-manual.c'), 'type' : 'manual', diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fa0b830196..ba2b035821 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -322,6 +322,7 @@ static int dhcp_server_send_unicast_raw( .ll.sll_ifindex = server->ifindex, .ll.sll_halen = hlen, }; + int r; assert(server); assert(server->ifindex > 0); @@ -336,9 +337,16 @@ static int dhcp_server_send_unicast_raw( if (len > UINT16_MAX) return -EOVERFLOW; - dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, - DHCP_PORT_CLIENT, len, -1); + r = dhcp_packet_append_ip_headers( + packet, + server->address, + DHCP_PORT_SERVER, + packet->dhcp.yiaddr, + DHCP_PORT_CLIENT, + len, + /* ip_service_type= */ -1); + if (r < 0) + return r; return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); } diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 97802e2c16..b2cadc07e5 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -17,9 +17,9 @@ #include "dhcp-duid-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" -#include "dhcp-packet.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "ip-util.h" #include "log.h" #include "tests.h" @@ -107,16 +107,6 @@ TEST(dhcp_client_anonymize) { ASSERT_OK_ZERO(sd_dhcp_client_set_request_option(client, 101)); } -TEST(dhcp_packet_checksum) { - uint8_t buf[20] = { - 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff - }; - - ASSERT_EQ(dhcp_packet_checksum(buf, 20), be16toh(0x78ae)); -} - TEST(dhcp_identifier_set_iaid) { uint32_t iaid_legacy; be32_t iaid; @@ -181,13 +171,13 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const discover->ip.ttl = 0; discover->ip.check = discover->udp.len; - udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8); + udp_check = ~ip_checksum(&discover->ip.ttl, len - 8); ASSERT_EQ(udp_check, 0xffff); discover->ip.ttl = IPDEFTTL; discover->ip.check = ip_check; - ip_check = ~dhcp_packet_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); + ip_check = ~ip_checksum((uint8_t*) &discover->ip, sizeof(discover->ip)); ASSERT_EQ(ip_check, 0xffff); ASSERT_NE(discover->dhcp.xid, 0u); diff --git a/src/libsystemd-network/test-ip-util.c b/src/libsystemd-network/test-ip-util.c new file mode 100644 index 0000000000..cf233ca91c --- /dev/null +++ b/src/libsystemd-network/test-ip-util.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "random-util.h" +#include "tests.h" + +TEST(ip_checksum) { + uint8_t buf[20] = { + 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + + ASSERT_EQ(ip_checksum(buf, 20), be16toh(0x78ae)); +} + +static void create_packet(struct iphdr *ip, struct udphdr *udp, struct iovec_wrapper *payload, struct iovec *ret) { + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + ASSERT_OK(iovw_put(&iovw, ip, sizeof(struct iphdr))); + ASSERT_OK(iovw_put(&iovw, udp, sizeof(struct udphdr))); + ASSERT_OK(iovw_put_iovw(&iovw, payload)); + ASSERT_OK(iovw_concat(&iovw, ret)); +} + +TEST(udp_packet_build_and_verify) { + size_t n = random_u64_range(20) + 20; + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + size_t i; + FOREACH_ARGUMENT(i, 1, 0, 1, 1, 3, 1, 2, 1, n, n, n + 1, n + 1, n + 2, n + 3, n + 4, n + 5, n + 6) { + struct iovec tmp = {}; + ASSERT_OK(random_bytes_allocate_iovec(i, &tmp)); + ASSERT_OK(iovw_consume_iov(&payload, &tmp)); + } + + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &payload, + &ip, + &udp)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&payload, &joined)); + + struct iphdr ip2; + struct udphdr udp2; + ASSERT_OK(udp_packet_build( + /* source_addr= */ htobe32(0xC0020001), + /* source_port= */ 42, + /* destination_addr= */ htobe32(0xC0020002), + /* destination_port= */ 43, + /* ip_service_type= */ 7, + &(struct iovec_wrapper) { + .iovec = &joined, + .count = 1, + }, + &ip2, + &udp2)); + + ASSERT_EQ(memcmp(&ip, &ip2, sizeof(struct iphdr)), 0); + ASSERT_EQ(memcmp(&udp, &udp2, sizeof(struct udphdr)), 0); + + _cleanup_(iovec_done) struct iovec packet = {}; + create_packet(&ip, &udp, &payload, &packet); + + struct iovec iov; + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + + /* UDP port mismatch */ + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 42, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* truncated packet */ + ASSERT_ERROR(udp_packet_verify(&IOVEC_MAKE(packet.iov_base, packet.iov_len - 1), + /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP version */ + struct iphdr badip = ip; + badip.version = 6; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header size */ + badip = ip; + badip.ihl = 1; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is smaller than IP header size */ + badip = ip; + badip.tot_len = htobe16(1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* packet size in IP header is larger than the packet size */ + badip = ip; + badip.tot_len = htobe16(be16toh(ip.tot_len) + 1); + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* IP protocol mismatch */ + badip = ip; + badip.protocol = IPPROTO_TCP; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad IP header checksum */ + badip = ip; + badip.check = ~ip.check; + iovec_done(&packet); + create_packet(&badip, &udp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the UDP header size */ + struct udphdr badudp = udp; + badudp.len = htobe16(1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is smaller than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) - 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* UDP length is larger than the packet size */ + badudp = udp; + badudp.len = htobe16(be16toh(udp.len) + 1); + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, /* ret_payload= */ NULL), EBADMSG); + + /* bad UDP checksum */ + badudp = udp; + if (udp.check != UINT16_MAX) + badudp.check = ~udp.check; + else + badudp.check = 0xdeadu; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_ERROR(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, /* ret_payload= */ NULL), EBADMSG); + + /* missing UDP checksum */ + badudp = udp; + badudp.check = 0; + iovec_done(&packet); + create_packet(&ip, &badudp, &payload, &packet); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ false, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); + ASSERT_OK(udp_packet_verify(&packet, /* port= */ 43, /* checksum= */ true, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &joined)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);