]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ip-util: introduce udp_packet_verify()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 16 Mar 2026 15:11:25 +0000 (00:11 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 24 Apr 2026 22:25:18 +0000 (07:25 +0900)
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
src/libsystemd-network/ip-util.c
src/libsystemd-network/ip-util.h
src/libsystemd-network/test-ip-util.c

index bc8f948d2ca4d668926c67140ad897bf8d54af48..27b09a25bbb4faa9f2f70e3964de6f7160962b3a 100644 (file)
@@ -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);
 }
index fcc22e15e3578c17b6c16e85f11281d08db3e278..3f062a7ef004e77e7f64670ac38b01087ea11e72 100644 (file)
@@ -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;
+}
index b42ff38af23667d09b43bb81fcac2fac60d460e8..fbc602ace5958f1f6249933dab5f36f97530ef7a 100644 (file)
@@ -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);
index 58adb8f48ef2602d1ea6e458ba12b163766fa57d..cf233ca91c57586f81e5458d2956bb30567c7231 100644 (file)
@@ -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);