]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ip-util: introduce udp_packet_build()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 15 Mar 2026 04:57:33 +0000 (13:57 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 24 Apr 2026 22:22:28 +0000 (07:22 +0900)
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
src/libsystemd-network/dhcp-packet.c
src/libsystemd-network/dhcp-packet.h
src/libsystemd-network/ip-util.c
src/libsystemd-network/ip-util.h
src/libsystemd-network/sd-dhcp-server.c

index a802cb5e86744211d27f9f39486f936f53cb2429..56306fbf53647c0144d3c0d74c826a1653bfbd6e 100644 (file)
@@ -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,
index 3b17ad8ac427aa77239c10c2b6e646e4275a1525..bc8f948d2ca4d668926c67140ad897bf8d54af48 100644 (file)
@@ -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);
-
-        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);
+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) {
+
+        struct iphdr ip;
+        struct udphdr udp;
+        int r;
 
-        packet->ip.check = packet->udp.len;
-        packet->udp.check = ip_checksum(&packet->ip.ttl, len - 8);
+        assert(packet);
+        assert(len > offsetof(DHCPPacket, dhcp));
+
+        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->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) {
index 8a56383adda6112bbac09800f787bba465a98242..90ea2caac987ac46130d513df7b6d6649dafc0b6 100644 (file)
@@ -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,
index dd7e872b2f001e1fe409a8666b6811ac4ed736ed..fcc22e15e3578c17b6c16e85f11281d08db3e278 100644 (file)
@@ -3,8 +3,22 @@
 #include <string.h>
 
 #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;
+}
index dd11afbadac25abfe7c3842bbdb1522332e27a77..b42ff38af23667d09b43bb81fcac2fac60d460e8 100644 (file)
@@ -1,6 +1,25 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+
 #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);
index fa0b83019698322c2487d62e44ad7efb3508e50e..ba2b035821918af13d2fe0ee94a66d40a5c32332 100644 (file)
@@ -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);
 }