From: Yu Watanabe Date: Sun, 15 Mar 2026 04:57:33 +0000 (+0900) Subject: ip-util: introduce udp_packet_build() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e8497d682586d3b6d1075b6fb613a0487639d21d;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/libsystemd-network/dhcp-client-send.c b/src/libsystemd-network/dhcp-client-send.c index a802cb5e867..56306fbf536 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 3b17ad8ac42..bc8f948d2ca 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); - - 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) { diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h index 8a56383adda..90ea2caac98 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 dd7e872b2f0..fcc22e15e35 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 dd11afbadac..b42ff38af23 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 fa0b8301969..ba2b0358219 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); }