From 8fe609445033d1e3b00dd75f1b4812a66abc8339 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 14:45:42 +0900 Subject: [PATCH] dhcp-message: introduce dhcp_message_send_{udp,raw}() --- src/libsystemd-network/dhcp-message.c | 124 +++++++++++++++++++++ src/libsystemd-network/dhcp-message.h | 17 +++ src/libsystemd-network/test-dhcp-message.c | 93 ++++++++++++++++ 3 files changed, 234 insertions(+) diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c index 0766458830..ae12c5b46a 100644 --- a/src/libsystemd-network/dhcp-message.c +++ b/src/libsystemd-network/dhcp-message.c @@ -20,6 +20,7 @@ #include "json-util.h" #include "network-common.h" #include "set.h" +#include "socket-util.h" #include "sort-util.h" #include "string-util.h" #include "unaligned.h" @@ -1645,3 +1646,126 @@ int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret) { *ret = TAKE_PTR(message); return 0; } + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port) { + + int r; + + assert(message); + assert(fd >= 0); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(dst_port), + .in.sin_addr.s_addr = dst_addr, + }; + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + + struct msghdr msg = { + .msg_name = &sa, + .msg_namelen = sizeof(sa.in), + .msg_iov = payload.iovec, + .msg_iovlen = payload.count, + }; + + if (src_addr != INADDR_ANY) { + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); + pktinfo->ipi_spec_dst.s_addr = src_addr; + } + + if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type) { + + int r; + + assert(message); + assert(fd >= 0); + assert(ifindex > 0); + assert(dst_hw_addr); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + struct iphdr ip; + struct udphdr udp; + r = udp_packet_build( + src_addr, + src_port, + dst_addr, + dst_port, + ip_service_type, + &payload, + &ip, + &udp); + if (r < 0) + return r; + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); + if (r < 0) + return r; + + r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); + if (r < 0) + return r; + + r = iovw_put_iovw(&iovw, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = dst_hw_addr->length, + }; + + memcpy_safe(sa.ll.sll_addr, dst_hw_addr->bytes, dst_hw_addr->length); + + struct msghdr mh = { + .msg_name = &sa.sa, + .msg_namelen = sockaddr_ll_len(&sa.ll), + .msg_iov = iovw.iovec, + .msg_iovlen = iovw.count, + }; + + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h index 41b22d2488..2e7e74def3 100644 --- a/src/libsystemd-network/dhcp-message.h +++ b/src/libsystemd-network/dhcp-message.h @@ -93,3 +93,20 @@ int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret); int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret); + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port); +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c index 5194cffcc2..b38220aef5 100644 --- a/src/libsystemd-network/test-dhcp-message.c +++ b/src/libsystemd-network/test-dhcp-message.c @@ -12,10 +12,13 @@ #include "dns-packet.h" #include "dns-resolver-internal.h" #include "ether-addr-util.h" +#include "fd-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" +#include "ip-util.h" #include "random-util.h" #include "set.h" +#include "socket-util.h" #include "strv.h" #include "tests.h" @@ -189,6 +192,92 @@ static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_w ASSERT_TRUE(iovw_equal(&iovw, expected)); } +static void verify_send_udp(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_udp( + message, + socket_fd[0], + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &IOVEC_MAKE(buf, len), + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +static void verify_send_raw(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_raw( + message, + socket_fd[0], + /* ifindex= */ 42, + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* src_port= */ DHCP_PORT_SERVER, + /* dst_hw_addr= */ &(struct hw_addr_data) { + .length = 6, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }}, + }, + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT, + /* ip_service_type= */ IPTOS_CLASS_CS6)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + + struct iovec payload; + ASSERT_OK(udp_packet_verify( + &IOVEC_MAKE(buf, len), + DHCP_PORT_CLIENT, + /* checksum= */ true, + &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + TEST(dhcp_message) { _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; @@ -452,6 +541,10 @@ TEST(dhcp_message) { _cleanup_(iovw_done_free) struct iovec_wrapper iovw3 = {}; ASSERT_OK(dhcp_message_build(m3, &iovw3)); ASSERT_TRUE(iovw_equal(&iovw, &iovw3)); + + /* send */ + verify_send_udp(m, xid, &hw_addr); + verify_send_raw(m, xid, &hw_addr); } static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) {