dhcp-message: introduce dhcp_message_send_{udp,raw}()

This commit is contained in:
Yu Watanabe
2026-05-16 22:33:00 +09:00
parent a0a6a614ce
commit 8fe6094450
3 changed files with 234 additions and 0 deletions
+124
View 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;
}
+17
View 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);
@@ -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) {