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.
This commit is contained in:
Yu Watanabe
2026-04-25 07:22:28 +09:00
parent 7c87767d14
commit e8497d6825
6 changed files with 185 additions and 28 deletions
+3 -1
View 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,
+32 -23
View 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);
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) {
+1 -1
View 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,
+119
View 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;
}
+19
View 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);
+11 -3
View 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);
}