]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dhcp-message: introduce dhcp_message_send_{udp,raw}()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 4 May 2026 05:45:42 +0000 (14:45 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 16 May 2026 13:33:00 +0000 (22:33 +0900)
src/libsystemd-network/dhcp-message.c
src/libsystemd-network/dhcp-message.h
src/libsystemd-network/test-dhcp-message.c

index 07664588301de0d91dff0d9d7568d966819828d8..ae12c5b46a97646458d4f8125fdd1954d3f4b8ed 100644 (file)
@@ -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;
+}
index 41b22d248809231cd024f743099718111bb57020..2e7e74def3eb26f4237558ed2e7502180c80a1cf 100644 (file)
@@ -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);
index 5194cffcc24ecfed554811107fcf55919ca2d5c5..b38220aef525975fbd826fd7ff192a1b6be49489 100644 (file)
 #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) {