From 7c87767d14ee2a26df8516357e8be56d882b66c6 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 15 Mar 2026 13:30:38 +0900 Subject: [PATCH 1/4] ip-util: introduce ip_checksum() It is equivalent to dhcp_packet_checksum(). It is generic and not specific to DHCP. Hence, renamed to ip_checksum(). --- src/libsystemd-network/dhcp-packet.c | 46 +++-------------------- src/libsystemd-network/dhcp-packet.h | 2 - src/libsystemd-network/ip-util.c | 38 +++++++++++++++++++ src/libsystemd-network/ip-util.h | 6 +++ src/libsystemd-network/meson.build | 4 ++ src/libsystemd-network/test-dhcp-client.c | 16 ++------ src/libsystemd-network/test-ip-util.c | 16 ++++++++ 7 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/libsystemd-network/ip-util.c create mode 100644 src/libsystemd-network/ip-util.h create mode 100644 src/libsystemd-network/test-ip-util.c diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 90eae88379..3b17ad8ac4 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -4,10 +4,10 @@ ***/ #include -#include #include "dhcp-option.h" #include "dhcp-packet.h" +#include "ip-util.h" #include "log.h" #include "memory-util.h" @@ -79,41 +79,6 @@ 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; - - /* See RFC1071 */ - - while (buf_64 < end_64) { - sum += *buf_64; - if (sum < *buf_64) - /* wrap around in one's complement */ - sum++; - - buf_64++; - } - - 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) { @@ -136,11 +101,11 @@ void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, 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->udp.check = ip_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.check = ip_checksum(&packet->ip, DHCP_IP_SIZE); } int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port) { @@ -193,7 +158,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui if all the other checks have passed */ - if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) + if (ip_checksum(&packet->ip, hdrlen)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ignoring packet: invalid IP checksum"); @@ -201,8 +166,7 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui packet->ip.check = packet->udp.len; packet->ip.ttl = 0; - if (dhcp_packet_checksum(&packet->ip.ttl, - be16toh(packet->udp.len) + 12)) + if (ip_checksum(&packet->ip.ttl, be16toh(packet->udp.len) + 12)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ignoring packet: invalid UDP checksum"); } diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 967bd5d89d..8a56383add 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,8 +23,6 @@ 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( DHCPPacket *packet, be32_t source_addr, diff --git a/src/libsystemd-network/ip-util.c b/src/libsystemd-network/ip-util.c new file mode 100644 index 0000000000..dd7e872b2f --- /dev/null +++ b/src/libsystemd-network/ip-util.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "iovec-util.h" +#include "ip-util.h" + +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))); +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h new file mode 100644 index 0000000000..dd11afbada --- /dev/null +++ b/src/libsystemd-network/ip-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +uint16_t ip_checksum(const void *buf, size_t len); 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/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..58adb8f48e --- /dev/null +++ b/src/libsystemd-network/test-ip-util.c @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ip-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)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From e8497d682586d3b6d1075b6fb613a0487639d21d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 15 Mar 2026 13:57:33 +0900 Subject: [PATCH 2/4] ip-util: introduce udp_packet_build() Then make dhcp_packet_append_ip_headers() just a wrapper of the new function. Currently, the wrapper is inefficient, but will be removed in a later commit. --- src/libsystemd-network/dhcp-client-send.c | 4 +- src/libsystemd-network/dhcp-packet.c | 55 +++++----- src/libsystemd-network/dhcp-packet.h | 2 +- src/libsystemd-network/ip-util.c | 119 ++++++++++++++++++++++ src/libsystemd-network/ip-util.h | 19 ++++ src/libsystemd-network/sd-dhcp-server.c | 14 ++- 6 files changed, 185 insertions(+), 28 deletions(-) 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 3b17ad8ac4..bc8f948d2c 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -7,6 +7,8 @@ #include "dhcp-option.h" #include "dhcp-packet.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.h" #include "log.h" #include "memory-util.h" @@ -79,33 +81,40 @@ int dhcp_message_init( return 0; } -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); +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) { - if (ip_service_type >= 0) - packet->ip.tos = ip_service_type; - else - packet->ip.tos = IPTOS_CLASS_CS6; + struct iphdr ip; + struct udphdr udp; + int r; - packet->ip.protocol = IPPROTO_UDP; - packet->ip.saddr = source_addr; - packet->ip.daddr = destination_addr; + assert(packet); + assert(len > offsetof(DHCPPacket, dhcp)); - packet->udp.source = htobe16(source_port); - packet->udp.dest = htobe16(destination_port); + 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; - packet->udp.len = htobe16(len - DHCP_IP_SIZE); - - packet->ip.check = packet->udp.len; - packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8); - - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = ip_checksum(&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) { diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 8a56383add..90ea2caac9 100644 --- a/src/libsystemd-network/dhcp-packet.h +++ b/src/libsystemd-network/dhcp-packet.h @@ -23,7 +23,7 @@ int dhcp_message_init( size_t optlen, size_t *ret_optoffset); -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 index dd7e872b2f..fcc22e15e3 100644 --- a/src/libsystemd-network/ip-util.c +++ b/src/libsystemd-network/ip-util.c @@ -3,8 +3,22 @@ #include #include "iovec-util.h" +#include "iovec-wrapper.h" #include "ip-util.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) @@ -36,3 +50,108 @@ 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; +} diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index dd11afbada..b42ff38af2 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -1,6 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include +#include + #include "sd-forward.h" +#include "sparse-endian.h" + +/* 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); 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); } From 11def23206d68d4a37b4ddc6b5a1b67c49554b30 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 17 Mar 2026 00:11:25 +0900 Subject: [PATCH 3/4] ip-util: introduce udp_packet_verify() This is mostly equivalent to dhcp_packet_verify_headers(), but - it optionally returns the UDP payload as iovec, and - supports IP header with options, - check packet length more strictly. --- src/libsystemd-network/dhcp-packet.c | 65 +---------- src/libsystemd-network/ip-util.c | 94 +++++++++++++++ src/libsystemd-network/ip-util.h | 6 + src/libsystemd-network/test-ip-util.c | 157 ++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 64 deletions(-) diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index bc8f948d2c..27b09a25bb 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -10,7 +10,6 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" -#include "log.h" #include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -118,67 +117,5 @@ int dhcp_packet_append_ip_headers( } 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 (ip_checksum(&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 (ip_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/ip-util.c b/src/libsystemd-network/ip-util.c index fcc22e15e3..3f062a7ef0 100644 --- a/src/libsystemd-network/ip-util.c +++ b/src/libsystemd-network/ip-util.c @@ -5,6 +5,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "ip-util.h" +#include "log.h" union iphdr_union { struct iphdr ip; @@ -155,3 +156,96 @@ int udp_packet_build( *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 index b42ff38af2..fbc602ace5 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -23,3 +23,9 @@ int udp_packet_build( 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/test-ip-util.c b/src/libsystemd-network/test-ip-util.c index 58adb8f48e..cf233ca91c 100644 --- a/src/libsystemd-network/test-ip-util.c +++ b/src/libsystemd-network/test-ip-util.c @@ -1,6 +1,9 @@ /* 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) { @@ -13,4 +16,158 @@ TEST(ip_checksum) { 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); From 3b4e01637f45dad8bf9fde6da05ad43a14a5e92b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 14 Apr 2026 08:12:49 +0900 Subject: [PATCH 4/4] ip-util: define IPV4_MIN_REASSEMBLY_SIZE The number will be later used at several places. --- src/libsystemd-network/ip-util.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libsystemd-network/ip-util.h b/src/libsystemd-network/ip-util.h index fbc602ace5..31fa4631c3 100644 --- a/src/libsystemd-network/ip-util.h +++ b/src/libsystemd-network/ip-util.h @@ -8,6 +8,12 @@ #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))