From 06ce0f90a57e35f9fbbbe4dff15ae5ae17381034 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 28 Apr 2026 13:27:43 +0900 Subject: [PATCH 1/9] sd-dhcp-relay: introduce new DHCP relay agent Previously, sd-dhcp-server can be run as a DHCP relay agent. But, DHCP server and DHCP relay agent behave completely differently, hence there is almost no code that can be shared between the two modes. Let's split out the DHCP relay agent feature from sd-dhcp-server. The new DHCP relay agent supports: - multiple upstream and downstream interfaces, - gateway address (giaddr field in DHCP message header) is configurable, - supports more DHCP relay agent information sub-options, - each interface has their own socket fd, and each socket is bound to the interface, so that we can enable/disable each interface safely without affecting other interfaces, and we can filter out any unexpected packets from unmanaged interfaces, networkd integration and test cases will be added later. --- .../dhcp-relay-downstream.c | 503 ++++++++++++++++++ src/libsystemd-network/dhcp-relay-interface.c | 334 ++++++++++++ src/libsystemd-network/dhcp-relay-internal.h | 114 ++++ src/libsystemd-network/dhcp-relay-upstream.c | 150 ++++++ src/libsystemd-network/meson.build | 4 + src/libsystemd-network/sd-dhcp-relay.c | 136 +++++ src/libsystemd/sd-common/sd-forward.h | 4 +- src/systemd/meson.build | 1 + src/systemd/sd-dhcp-relay.h | 71 +++ 9 files changed, 1316 insertions(+), 1 deletion(-) create mode 100644 src/libsystemd-network/dhcp-relay-downstream.c create mode 100644 src/libsystemd-network/dhcp-relay-interface.c create mode 100644 src/libsystemd-network/dhcp-relay-internal.h create mode 100644 src/libsystemd-network/dhcp-relay-upstream.c create mode 100644 src/libsystemd-network/sd-dhcp-relay.c create mode 100644 src/systemd/sd-dhcp-relay.h diff --git a/src/libsystemd-network/dhcp-relay-downstream.c b/src/libsystemd-network/dhcp-relay-downstream.c new file mode 100644 index 0000000000..dd825d02a2 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-downstream.c @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "siphash24.h" +#include "socket-util.h" + +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->circuit_id, iov); +} + +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->vss, iov); +} + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options) { + assert(interface); + assert(!interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + return unref_and_replace_new_ref(interface->extra_options, options, tlv_ref, tlv_unref); +} + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + if (address) + interface->gateway_address = *address; + else + interface->gateway_address = (struct in_addr) {}; + + return 0; +} + +static void downstream_hash_func(const sd_dhcp_relay_interface *interface, struct siphash *state) { + int b; + + assert(interface); + assert(!interface->upstream); + assert(state); + + siphash24_compress_typesafe(interface->gateway_address, state); + + b = iovec_is_set(&interface->circuit_id); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->circuit_id, state); + + b = iovec_is_set(&interface->vss); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->vss, state); +} + +static int downstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + int r; + + assert(a); + assert(!a->upstream); + assert(b); + assert(!b->upstream); + + r = CMP(a->gateway_address.s_addr, b->gateway_address.s_addr); + if (r != 0) + return r; + + r = iovec_memcmp(&a->circuit_id, &b->circuit_id); + if (r != 0) + return r; + + return iovec_memcmp(&a->vss, &b->vss); +} + +DEFINE_PRIVATE_HASH_OPS( + downstream_hash_ops, + sd_dhcp_relay_interface, + downstream_hash_func, + downstream_compare_func); + +int downstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + /* Do not use a Set; otherwise, we cannot deduplicate entries. */ + return hashmap_ensure_put(&interface->relay->downstream_interfaces, &downstream_hash_ops, interface, interface); +} + +void downstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + + hashmap_remove_value(interface->relay->downstream_interfaces, interface, interface); +} + +void downstream_done(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(!interface->upstream); + + downstream_unregister(interface); + iovec_done(&interface->circuit_id); + iovec_done(&interface->vss); + interface->extra_options = tlv_unref(interface->extra_options); +} + +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret) { + int r; + + assert(relay); + assert(message); + + /* RFC 3046 section 2.2: + * DHCP servers claiming to support the Relay Agent Information option SHALL echo the entire contents + * of the Relay Agent Information option in all replies. + * + * So, first try to find the suitable downstream interface by the gateway address and circuit ID in + * the reply message. */ + sd_dhcp_relay_interface key = { + .gateway_address.s_addr = message->header.giaddr, + }; + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + return r; + + if (agent_info) { + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &key.circuit_id); + if (r < 0 && r != -ENODATA) + return r; + + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &key.vss); + if (r < 0 && r != -ENODATA) + return r; + } + + sd_dhcp_relay_interface *interface = hashmap_get(relay->downstream_interfaces, &key); + if (!interface) { + /* Some DHCP servers may not understand the Relay Agent Information option and may not echo + * it back. To support this case, we fall back to finding a suitable downstream interface + * using only the gateway address. Note that if the downstream network uses VRF or the Link + * Selection sub-option, multiple interfaces may share the same gateway address. In such + * cases, we cannot reliably determine the correct downstream interface, so we must drop the + * packet. */ + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, relay->downstream_interfaces) { + if (i->gateway_address.s_addr != message->header.giaddr) + continue; + + if (interface) + /* multiple interfaces have the same gateway address?? */ + return -ENOTUNIQ; + + interface = i; + } + } + if (!interface) + return -ENODEV; + + assert(!interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +static int downstream_append_relay_agent_information( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(message); + + _cleanup_(tlv_done) TLV tlv = TLV_INIT(TLV_DHCP4_SUBOPTION); + + /* First, set per-interface options. */ + if (iovec_is_set(&interface->circuit_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &interface->circuit_id); + if (r < 0) + return r; + } + + if (iovec_is_set(&interface->vss)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &interface->vss); + if (r < 0) + return r; + } + + if (!in4_addr_equal(&interface->address, &interface->gateway_address)) { + /* RFC 3527 section 3 + * The link-selection sub-option is used by any DHCP relay agent that desires to specify a + * subnet/link for a DHCP client request that it is relaying but needs the subnet/link + * specification to be different from the IP address the DHCP server should use when + * communicating with the relay agent. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_LINK_SELECTION, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->extra_options); + if (r < 0) + return r; + + /* Then, set agent-wide options. */ + if (iovec_is_set(&interface->relay->remote_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_REMOTE_ID, &interface->relay->remote_id); + if (r < 0) + return r; + } + + if (interface->relay->server_identifier_override) { + /* RFC 5107 section 1: + * This DHCP relay agent suboption, Server Identifier Override, allows the relay agent to + * tell the DHCP server what value to place into the Server Identifier option. Using this, + * the relay agent can force a host in RENEWING state to send DHCPREQUEST messages to the + * relay agent instead of directly to the server. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + + /* RFC 5107 section 4: + * DHCP relay agents implementing this suboption SHOULD also implement and use the DHCPv4 + * Relay Agent Flags Suboption in order to specify whether the DHCP relay agent received the + * original message as a broadcast or unicast. */ + uint8_t flags = 0; + SET_FLAG(flags, DHCP_RELAY_AGENT_FLAG_UNICAST, + pktinfo && + pktinfo->ipi_addr.s_addr != INADDR_BROADCAST && + pktinfo->ipi_addr.s_addr != interface->subnet_broadcast.s_addr); + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_FLAGS, sizeof(uint8_t), &flags); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->relay->extra_options); + if (r < 0) + return r; + + if (tlv_isempty(&tlv)) + return 0; + + return dhcp_message_append_option_sub_tlv(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &tlv); +} + +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(iov); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* RFC 1542 section 4.1.1: + * The relay agent MUST silently discard BOOTREQUEST messages whose 'hops' field exceeds the value 16. */ + if (message->header.hops >= 16) + return 0; + message->header.hops++; + + /* RFC 3046 section 2.1.1: + * Relay agents configured to add a Relay Agent option which receive a client DHCP packet with a + * nonzero giaddr SHALL discard the packet if the giaddr spoofs a giaddr address implemented by the + * local agent itself. */ + if (message->header.giaddr != INADDR_ANY) { + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, interface->relay->interfaces) { + if (i->upstream) + continue; + if (message->header.giaddr == i->address.s_addr || + message->header.giaddr == i->gateway_address.s_addr) + return -EBADMSG; + } + } + + /* RFC 1542 section 4.1.1: + * If the relay agent does decide to relay the request, it MUST examine the 'giaddr' ("gateway" IP + * address) field. If this field is zero, the relay agent MUST fill this field with the IP address of + * the interface on which the request was received. (snip) If the 'giaddr' field contains some + * non-zero value, the 'giaddr' field MUST NOT be modified. + * + * RFC 3046 section 2.1.1: + * the relay agent SHALL forward any received DHCP packet with a valid non-zero giaddr WITHOUT adding + * any relay agent options. Per RFC 2131, it shall also NOT modify the giaddr value. + * + * Therefore, we set giaddr and the Relay Agent Information option here only when the giaddr in the + * received message is zero. */ + if (message->header.giaddr == INADDR_ANY) { + message->header.giaddr = interface->gateway_address.s_addr; + + /* RFC 3046 section 2.1: + * Relay agents receiving a DHCP packet from an untrusted circuit with giaddr set to zero + * (indicating that they are the first-hop router) but with a Relay Agent Information option + * already present in the packet SHALL discard the packet and increment an error count. */ + if (dhcp_message_has_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)) + return -EBADMSG; + + r = downstream_append_relay_agent_information(interface, message, pktinfo); + if (r < 0) + return r; + } + + log_dhcp_relay_interface(interface, "Received BOOTREQUEST (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *upstream; + r = upstream_get(interface->relay, &upstream); + if (r < 0) + return r; + + return upstream_send_message(upstream, message); +} + +static int downstream_acquire_raw_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + assert(!interface->upstream); + + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it. */ + return interface->socket_fd; + + if (interface->raw_socket_fd >= 0) + /* Already opened. */ + return interface->raw_socket_fd; + + /* This is a send-only socket, hence it is opened with protocol=0, and do not call bind(). + * The interface binding will be done on send. */ + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, /* protocol= */ 0)); + if (fd < 0) + return fd; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + return interface->raw_socket_fd = TAKE_FD(fd); +} + +static int downstream_send_l2_unicast( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct hw_addr_data *hw_addr) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.yiaddr != INADDR_ANY); + assert(!hw_addr_is_null(hw_addr)); + + int fd = downstream_acquire_raw_socket(interface); + if (fd < 0) + return fd; + + r = dhcp_message_send_raw( + message, + fd, + interface->ifindex, + interface->address.s_addr, + interface->port, + hw_addr, + message->header.yiaddr, + DHCP_PORT_CLIENT, + interface->ip_service_type); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (L2 unicast).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = message->header.yiaddr })); + return 0; +} + +static int downstream_send_udp( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + be32_t address) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(address != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + address, + DHCP_PORT_CLIENT); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = address })); + return 0; +} + +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.op == BOOTREPLY); + + /* See RFC 2131 Section 4.1 + * + * (Note, we are a relay agent, hence conditions for giaddr in the statements are ignored.) */ + + uint8_t type; + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &type); + if (r < 0) + return r; + + /* the server broadcasts any DHCPNAK messages to 0xffffffff. */ + if (type == DHCP_NAK) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If (...) the ’ciaddr’ field is nonzero, then the server unicasts DHCPOFFER and DHCPACK messages + * to the address in ’ciaddr’. */ + if (message->header.ciaddr != INADDR_ANY) + return downstream_send_udp(interface, message, message->header.ciaddr); + + /* If (...) ’ciaddr’ is zero, and the broadcast bit is set, then the server broadcasts DHCPOFFER + * and DHCPACK messages to 0xffffffff. + * + * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. + * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, for other + * message types we do not support, also broadcast if 'yiaddr' is zero.) */ + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If the broadcast bit is not set (...) and ’ciaddr’ is zero, then the server unicasts DHCPOFFER + * and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ + return downstream_send_l2_unicast(interface, message, &hw_addr); +} diff --git a/src/libsystemd-network/dhcp-relay-interface.c b/src/libsystemd-network/dhcp-relay-interface.c new file mode 100644 index 0000000000..218f56fc04 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-interface.c @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "socket-util.h" +#include "string-util.h" + +static sd_dhcp_relay_interface* dhcp_relay_interface_free(sd_dhcp_relay_interface *interface) { + if (!interface) + return NULL; + + assert(interface->relay); + + sd_event_source_disable_unref(interface->io_event_source); + safe_close(interface->socket_fd); + safe_close(interface->raw_socket_fd); + + if (interface->upstream) + upstream_done(interface); + else + downstream_done(interface); + + hashmap_remove_value(interface->relay->interfaces, INT_TO_PTR(interface->ifindex), interface); + sd_dhcp_relay_unref(interface->relay); + + free(interface->ifname); + return mfree(interface); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface, dhcp_relay_interface_free); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret) { + int r; + + assert_return(relay, -EINVAL); + assert_return(ifindex > 0 || (ifindex == DHCP_RELAY_IFINDEX_UNBOUND && !!is_upstream), -EINVAL); + assert_return(ret, -EINVAL); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = new(sd_dhcp_relay_interface, 1); + if (!interface) + return -ENOMEM; + + /* RFC 1542 section 5.4: + * The server SHOULD next check the 'giaddr' field. If this field is non-zero, the server SHOULD send + * the BOOTREPLY as an IP unicast to the IP address identified in the 'giaddr' field. The UDP + * destination port MUST be set to BOOTPS (67). + * + * Hence, the relay agent needs to use DHCP_PORT_SERVER (67) for both source and destination port. */ + *interface = (sd_dhcp_relay_interface) { + .n_ref = 1, + .relay = sd_dhcp_relay_ref(relay), + .upstream = !!is_upstream, + .ifindex = ifindex, + .port = DHCP_PORT_SERVER, + .socket_fd = -EBADF, + .raw_socket_fd = -EBADF, + .ip_service_type = IPTOS_CLASS_CS6, /* Defaults to CS6 (Internetwork Control). */ + }; + + r = hashmap_ensure_put(&relay->interfaces, NULL, INT_TO_PTR(interface->ifindex), interface); + if (r < 0) + return r; + + *ret = TAKE_PTR(interface); + return 0; +} + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname) { + assert_return(interface, -EINVAL); + + return free_and_strdup(&interface->ifname, ifname); +} + +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret) { + int r; + + assert_return(interface, -EINVAL); + + r = get_ifname(interface->ifindex, &interface->ifname); + if (r < 0) + return r; + + if (ret) + *ret = interface->ifname; + + return 0; +} + +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + assert_return(prefixlen <= sizeof(struct in_addr) * 8, -EINVAL); + + if (address) + interface->address = *address; + else + interface->address = (struct in_addr) {}; + + if (in4_addr_is_set(&interface->address)) { + interface->address_prefixlen = prefixlen; + + struct in_addr netmask; + in4_addr_prefixlen_to_netmask(&netmask, prefixlen); + interface->subnet_broadcast.s_addr = (interface->address.s_addr & netmask.s_addr) | ~netmask.s_addr; + } else { + interface->address_prefixlen = 0; + interface->subnet_broadcast = (struct in_addr) {}; + } + + return 0; +} + +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen) { + assert_return(interface, -EINVAL); + + if (ret_address) + *ret_address = interface->address; + if (ret_prefixlen) + *ret_prefixlen = interface->address_prefixlen; + + return in4_addr_is_set(&interface->address); +} + +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->port = port; + return 0; +} + +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->ip_service_type = type; + return 0; +} + +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface) { + return interface && sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0; +} + +static int interface_open_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + if (interface->ifindex > 0) { + r = socket_bind_to_ifindex(fd, interface->ifindex); + if (r < 0) + return r; + } + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, interface->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(interface->port), + .in.sin_addr.s_addr = INADDR_ANY, + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +static int interface_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay_interface *interface = ASSERT_PTR(userdata); + int r; + + assert(fd >= 0); + + ssize_t buflen = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + return 0; + if (buflen < 0) { + log_dhcp_relay_interface_errno( + interface, buflen, + "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) + return log_oom_debug(); + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + + ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); + if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) + return 0; + if (len < 0) { + log_dhcp_relay_interface_errno( + interface, len, + "Could not receive message, ignoring: %m"); + return 0; + } + + if (interface->upstream) + r = upstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + else + r = downstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + if (r < 0) + log_dhcp_relay_interface_errno( + interface, r, + "Could not process message, ignoring: %m"); + + return 0; +} + +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface) { + int r; + + assert_return(interface, -EINVAL); + assert_return(interface->relay, -ESTALE); + assert_return(interface->relay->event, -EINVAL); + + if (in4_addr_is_null(&interface->relay->server_address) || + (!interface->upstream && in4_addr_is_null(&interface->address)) || + (!interface->upstream && in4_addr_is_null(&interface->gateway_address))) + return -EADDRNOTAVAIL; + + if (sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0) + return 0; /* Already started. */ + + _cleanup_close_ int fd_close = -EBADF; + int fd; + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket + * even if we fail to set up the event source. */ + fd = interface->socket_fd; + else { + /* Otherwise, open a new socket. */ + fd = fd_close = interface_open_socket(interface); + if (fd < 0) + return fd; + } + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(interface->relay->event, &s, fd, EPOLLIN, + interface_receive_message, interface); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, interface->relay->event_priority); + if (r < 0) + return r; + + const char *name, *description; + if (sd_dhcp_relay_interface_get_ifname(interface, &name) >= 0) + description = strjoina("dhcp-relay-interface-io-event-source-", name); + else + description = "dhcp-relay-interface-io-event-source"; + (void) sd_event_source_set_description(s, description); + + if (fd_close >= 0) { + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + TAKE_FD(fd_close); + } + + /* This may potentially fail, in which case the event source should be discarded. */ + if (interface->upstream) + r = upstream_register(interface); + else + r = downstream_register(interface); + if (r < 0) + return r; + + sd_event_source_disable_unref(interface->io_event_source); + interface->io_event_source = TAKE_PTR(s); + return 0; +} + +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface) { + if (!interface) + return 0; + + interface->raw_socket_fd = safe_close(interface->raw_socket_fd); + interface->io_event_source = sd_event_source_disable_unref(interface->io_event_source); + + if (interface->upstream) + upstream_unregister(interface); + else + downstream_unregister(interface); + return 0; +} diff --git a/src/libsystemd-network/dhcp-relay-internal.h b/src/libsystemd-network/dhcp-relay-internal.h new file mode 100644 index 0000000000..feb08a606c --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-internal.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp-relay.h" + +#include "dhcp-message.h" +#include "ether-addr-util.h" +#include "network-common.h" +#include "sd-forward.h" +#include "tlv-util.h" + +#define DHCP_RELAY_IFINDEX_UNBOUND (-100) + +struct sd_dhcp_relay { + unsigned n_ref; + + sd_event *event; + int event_priority; + + Hashmap *interfaces; /* All interfaces by their ifindex. */ + Prioq *upstream_interfaces; /* Active upstream interfaces by their priorities. */ + Hashmap *downstream_interfaces; /* Active downstream interfaces by their gateway address, circuit ID, and VSS. */ + + struct in_addr server_address; + uint16_t server_port; + + /* Global Relay Agent Information option (82) */ + struct iovec remote_id; /* Agent Remote ID Sub-option (2) */ + bool server_identifier_override; /* Relay Agent Flags (10) and Server Identifier Override Sub-option (11) */ + TLV *extra_options; +}; + +struct sd_dhcp_relay_interface { + unsigned n_ref; + + sd_dhcp_relay *relay; + bool upstream; + + int ifindex; + char *ifname; + + /* The address used for: + * - the source IP of forwarded packets (both downstream and upstream), + * - the Server Identifier Override Sub-option (when sd_dhcp_relay.server_identifier_override is true), + * - the Link Selection Sub-option (when address != gateway_address). + * Typically, this is an address of the interface itself, but we can specify an address of another + * interface (e.g., for IP unnumbered setups). */ + struct in_addr address; + uint8_t address_prefixlen; + /* subnet-directed broadcast address, e.g. 192.0.2.255 when address is 192.0.2.1/24. */ + struct in_addr subnet_broadcast; + uint16_t port; + + uint8_t ip_service_type; /* a.k.a. TOS */ + int socket_fd; /* socket fd set externally, used by unit tests */ + int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */ + sd_event_source *io_event_source; + + /* Mutually exclusive fields depending on the 'upstream' boolean */ + union { + /* Upstream specific */ + struct { + int priority; + unsigned priority_idx; + }; + + /* Downstream specific */ + struct { + /* The address set in the giaddr field of the DHCP message header. Typically, it is + * the same as 'address' above, but we can specify a different address, and it does + * not need to be an address assigned to the interface. */ + struct in_addr gateway_address; + + /* Per-interface Relay Agent Information option (82) */ + struct iovec circuit_id; /* Agent Circuit ID Sub-option (1) */ + struct iovec vss; /* DHCPv4 Virtual Subnet Selection Sub-Option (151) */ + TLV *extra_options; + }; + }; +}; + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options); + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options); +int downstream_register(sd_dhcp_relay_interface *interface); +void downstream_unregister(sd_dhcp_relay_interface *interface); +void downstream_done(sd_dhcp_relay_interface *interface); +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret); +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +int upstream_register(sd_dhcp_relay_interface *interface); +void upstream_unregister(sd_dhcp_relay_interface *interface); +void upstream_done(sd_dhcp_relay_interface *interface); +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret); +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +#define log_dhcp_relay_interface_errno(interface, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + error, fmt, ##__VA_ARGS__) +#define log_dhcp_relay_interface(interface, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/dhcp-relay-upstream.c b/src/libsystemd-network/dhcp-relay-upstream.c new file mode 100644 index 0000000000..f1b4189153 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-upstream.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "prioq.h" + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority) { + assert_return(interface, -EINVAL); + assert_return(interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->priority = priority; + return 0; +} + +static int upstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + assert(a); + assert(a->upstream); + assert(b); + assert(b->upstream); + + /* Higher priority first */ + return CMP(b->priority, a->priority); +} + +int upstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + interface->priority_idx = PRIOQ_IDX_NULL; + return prioq_ensure_put(&interface->relay->upstream_interfaces, upstream_compare_func, interface, &interface->priority_idx); +} + +void upstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + + (void) prioq_remove(interface->relay->upstream_interfaces, interface, &interface->priority_idx); +} + +void upstream_done(sd_dhcp_relay_interface *interface) { + upstream_unregister(interface); +} + +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret) { + sd_dhcp_relay_interface *interface = prioq_peek(relay->upstream_interfaces); + if (!interface) + return -ENETDOWN; + + assert(interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(iov); + + if (pktinfo && pktinfo->ipi_ifindex > 0 && + interface->ifindex < 0 && + hashmap_contains(interface->relay->interfaces, INT_TO_PTR(pktinfo->ipi_ifindex))) + return 0; /* This message is not for the catch-all interface. */ + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREPLY, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + if (message->header.giaddr == INADDR_ANY) + return 0; /* Not a relay message, so it is probably not for us. */ + + if (pktinfo && pktinfo->ipi_addr.s_addr != message->header.giaddr) + return -EBADMSG; + + log_dhcp_relay_interface(interface, "Received BOOTREPLY (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *downstream; + r = downstream_get(interface->relay, message, &downstream); + if (r < 0) + return r; + + /* RFC 3046 abstract: + * The DHCP Server echoes the option back verbatim to the relay agent in server-to-client + * replies, and the relay agent strips the option before forwarding the reply to the client. + * + * RFC 3046 section 2.1: + * The Relay Agent Information option echoed by a server MUST be removed by either the relay + * agent or the trusted downstream network element which added it when forwarding a + * server-to-client response back to the client. + * + * Here, we do not check the contents of the option, and unconditionally remove it. */ + dhcp_message_remove_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + return downstream_send_message(downstream, message); +} + +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(interface->upstream); + assert(message); + assert(message->header.op == BOOTREQUEST); + assert(message->header.giaddr != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + interface->relay->server_address.s_addr, + interface->relay->server_port); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREQUEST (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&interface->relay->server_address)); + return 0; +} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index b9b8c7062e..5d84b9934a 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -9,6 +9,9 @@ libsystemd_network_sources = files( 'dhcp-option.c', 'dhcp-packet.c', 'dhcp-protocol.c', + 'dhcp-relay-downstream.c', + 'dhcp-relay-interface.c', + 'dhcp-relay-upstream.c', 'dhcp-route.c', 'dhcp6-network.c', 'dhcp6-option.c', @@ -25,6 +28,7 @@ libsystemd_network_sources = files( 'sd-dhcp-client.c', 'sd-dhcp-duid.c', 'sd-dhcp-lease.c', + 'sd-dhcp-relay.c', 'sd-dhcp-server-lease.c', 'sd-dhcp-server.c', 'sd-dhcp6-client.c', diff --git a/src/libsystemd-network/sd-dhcp-relay.c b/src/libsystemd-network/sd-dhcp-relay.c new file mode 100644 index 0000000000..1442a43fda --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-relay.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "prioq.h" + +static sd_dhcp_relay* dhcp_relay_free(sd_dhcp_relay *relay) { + if (!relay) + return NULL; + + assert(hashmap_isempty(relay->interfaces)); + hashmap_free(relay->interfaces); + assert(hashmap_isempty(relay->downstream_interfaces)); + hashmap_free(relay->downstream_interfaces); + assert(prioq_isempty(relay->upstream_interfaces)); + prioq_free(relay->upstream_interfaces); + + sd_event_unref(relay->event); + + iovec_done(&relay->remote_id); + tlv_unref(relay->extra_options); + return mfree(relay); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay, sd_dhcp_relay, dhcp_relay_free); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret) { + assert_return(ret, -EINVAL); + + sd_dhcp_relay *relay = new(sd_dhcp_relay, 1); + if (!relay) + return -ENOMEM; + + *relay = (sd_dhcp_relay) { + .n_ref = 1, + .server_port = DHCP_PORT_SERVER, + }; + + *ret = TAKE_PTR(relay); + return 0; +} + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay) { + if (!relay) + return false; + + return + !prioq_isempty(relay->upstream_interfaces) || + !hashmap_isempty(relay->downstream_interfaces); +} + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority) { + int r; + + assert_return(relay, -EINVAL); + assert_return(!relay->event, -EBUSY); + + if (event) + relay->event = sd_event_ref(event); + else { + r = sd_event_default(&relay->event); + if (r < 0) + return r; + } + + relay->event_priority = priority; + return 0; +} + +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->event = sd_event_unref(relay->event); + return 0; +} + +sd_event* sd_dhcp_relay_get_event(sd_dhcp_relay *relay) { + assert_return(relay, NULL); + + return relay->event; +} + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + if (address) + relay->server_address = *address; + else + relay->server_address = (struct in_addr) {}; + + return 0; +} + +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret) { + assert_return(relay, -EINVAL); + + if (ret) + *ret = relay->server_address; + + return in4_addr_is_set(&relay->server_address); +} + +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->server_port = port; + return 0; +} + +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov) { + assert_return(relay, -EINVAL); + + return iovec_done_and_memdup(&relay->remote_id, iov); +} + +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b) { + assert_return(relay, -EINVAL); + + relay->server_identifier_override = !!b; + return 0; +} + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options) { + assert(relay); + + return unref_and_replace_new_ref(relay->extra_options, options, tlv_ref, tlv_unref); +} diff --git a/src/libsystemd/sd-common/sd-forward.h b/src/libsystemd/sd-common/sd-forward.h index 8abe655209..ba800bbd01 100644 --- a/src/libsystemd/sd-common/sd-forward.h +++ b/src/libsystemd/sd-common/sd-forward.h @@ -79,8 +79,10 @@ typedef struct sd_ipv4ll sd_ipv4ll; typedef struct sd_dhcp_client sd_dhcp_client; typedef struct sd_dhcp_lease sd_dhcp_lease; typedef struct sd_dhcp_route sd_dhcp_route; -typedef struct sd_dns_resolver sd_dns_resolver; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; typedef struct sd_dhcp_server sd_dhcp_server; +typedef struct sd_dns_resolver sd_dns_resolver; typedef struct sd_ndisc sd_ndisc; typedef struct sd_radv sd_radv; typedef struct sd_dhcp6_client sd_dhcp6_client; diff --git a/src/systemd/meson.build b/src/systemd/meson.build index ad455d73b2..ca5444310f 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -29,6 +29,7 @@ _not_installed_headers = [ 'sd-dhcp-duid.h', 'sd-dhcp-lease.h', 'sd-dhcp-protocol.h', + 'sd-dhcp-relay.h', 'sd-dhcp-server-lease.h', 'sd-dhcp-server.h', 'sd-dhcp6-client.h', diff --git a/src/systemd/sd-dhcp-relay.h b/src/systemd/sd-dhcp-relay.h new file mode 100644 index 0000000000..47c42a21dd --- /dev/null +++ b/src/systemd/sd-dhcp-relay.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcprelayhfoo +#define foosddhcprelayhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_event sd_event; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; + +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay); +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret); + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay); + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority); +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay); +sd_event *sd_dhcp_relay_get_event(sd_dhcp_relay *relay); + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address); +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret); +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port); +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov); +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret); + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname); +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret); +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen); +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen); +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port); +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type); +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface); + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address); +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov); +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov); + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay, sd_dhcp_relay_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface_unref); + +_SD_END_DECLARATIONS; + +#endif From 0325022c15803458a580fe3ea6c14a835b540765 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 23:18:32 +0900 Subject: [PATCH 2/9] test: add unit test for DHCP relay agent --- src/libsystemd-network/meson.build | 3 + src/libsystemd-network/test-dhcp-relay.c | 383 +++++++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 src/libsystemd-network/test-dhcp-relay.c diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 5d84b9934a..7693687a13 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -96,6 +96,9 @@ executables += [ network_test_template + { 'sources' : files('test-dhcp-option.c'), }, + network_test_template + { + 'sources' : files('test-dhcp-relay.c'), + }, network_test_template + { 'sources' : files('test-dhcp-server.c'), }, diff --git a/src/libsystemd-network/test-dhcp-relay.c b/src/libsystemd-network/test-dhcp-relay.c new file mode 100644 index 0000000000..dd4e7652d9 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-relay.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" + +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" /* IWYU pragma: keep */ +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "ip-util.h" +#include "socket-util.h" +#include "tests.h" + +static uint32_t xid = 12345; + +static const struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, +}; + +static unsigned fake_server_message_count = 0; +static unsigned fake_client_message_count = 0; + +TEST(sd_dhcp_relay_ref_unref) { + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL, *downstream = NULL; + + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_NOT_NULL(relay); + + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Each interface holds a reference to the sd_dhcp_relay object, so we can safely drop our reference. */ + relay = sd_dhcp_relay_unref(relay); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(downstream->relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Still upstream interface has the reference. */ + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(upstream->relay->interfaces, INT_TO_PTR(4343))); + + /* Everything should be freed with this. */ + upstream = sd_dhcp_relay_interface_unref(upstream); + + /* Let's check the inverse order. */ + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); + + upstream = sd_dhcp_relay_interface_unref(upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4242))); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); +} + +static void send_message(int fd, sd_dhcp_message *m) { + ASSERT_OK(dhcp_message_send_udp( + m, + fd, + /* src_addr= */ INADDR_ANY, + /* dst_addr= */ INADDR_ANY, + /* dst_port= */ 0)); +} + +static int fake_server_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay *relay = ASSERT_PTR(userdata); + + fake_server_message_count++; + log_debug("%s: count=%u", __func__, fake_server_message_count); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _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(fd, &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)); + + ASSERT_EQ(m->header.hops, 1u); + + sd_dhcp_relay_interface *downstream; + ASSERT_OK(downstream_get(relay, m, &downstream)); + ASSERT_FALSE(downstream->upstream); + + ASSERT_EQ(m->header.giaddr, downstream->gateway_address.s_addr); + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info)); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, agent_info->entries) { + uint32_t tag = PTR_TO_UINT32(key); + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(tlv_get_alloc(agent_info, tag, &iov)); + + switch (tag) { + case SD_DHCP_RELAY_AGENT_CIRCUIT_ID: + ASSERT_TRUE(iovec_equal(&iov, &downstream->circuit_id)); + break; + case SD_DHCP_RELAY_AGENT_REMOTE_ID: + ASSERT_TRUE(iovec_equal(&iov, &relay->remote_id)); + break; + case SD_DHCP_RELAY_AGENT_LINK_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_FLAGS: { + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_EQ(iov.iov_len, 1u); + uint8_t flags = *(uint8_t*) iov.iov_base; + /* In the unit test, we cannot detect if the message is broadcast or unicast because + * AF_UNIX is used; therefore, the unicast flag is not set. */ + ASSERT_FALSE(FLAGS_SET(flags, DHCP_RELAY_AGENT_FLAG_UNICAST)); + break; + } + case SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE: + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &downstream->vss)); + break; + default: + assert_not_reached(); + } + } + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + + switch (fake_server_message_count) { + case 1: + ASSERT_EQ(t, DHCP_DISCOVER); + break; + case 2: + ASSERT_EQ(t, DHCP_REQUEST); + break; + case 3: + ASSERT_EQ(t, DHCP_RELEASE); + + if (fake_client_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static void fake_client_verify(int fd, uint8_t type, bool raw) { + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _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(fd, &msg, MSG_DONTWAIT)); + + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) + ASSERT_OK(udp_packet_verify(&payload, DHCP_PORT_CLIENT, /* checksum= */ true, &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREPLY, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m)); + + ASSERT_EQ(m->header.hops, 0u); + ASSERT_FALSE(dhcp_message_has_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + ASSERT_EQ(t, type); +} + +static int fake_client_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + fake_client_message_count++; + log_debug("%s: count=%u", __func__, fake_client_message_count); + + switch (fake_client_message_count) { + case 1: + fake_client_verify(fd, DHCP_OFFER, /* raw= */ true); + break; + case 2: + fake_client_verify(fd, DHCP_ACK, /* raw= */ false); + break; + case 3: + fake_client_verify(fd, DHCP_NAK, /* raw= */ false); + + if (fake_server_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(forwarding) { + union in_addr_union a; + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + /* Setting up an upstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK_ZERO(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + ASSERT_OK_POSITIVE(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + /* IO event source for the server side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_server = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_server, upstream_fd[1], EPOLLIN, fake_server_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_server, SD_EVENT_PRIORITY_IMPORTANT)); + ASSERT_OK(sd_event_source_set_description(fake_server, "fake-server-io-event-source")); + + /* Setting up a downstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + /* IO event source for the client side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_client = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_client, downstream_fd[1], EPOLLIN, fake_client_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_client, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(sd_event_source_set_description(fake_client, "fake-client-io-event-source")); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* Test: downstream -> upstream */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 100)); + m->header.op = BOOTREPLY; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREQUEST; + + /* Invalid message (too large hops). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 101)); + m->header.hops = 16; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.hops = 0; + + /* Invalid message (invalid giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 102)); + m->header.giaddr = downstream->address.s_addr; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = INADDR_ANY; + + /* Invalid message (unexpected relay agent information). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 103)); + ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + dhcp_message_remove_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_RELEASE)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Test: upstream -> downstream */ + m->header.op = BOOTREPLY; + m->header.giaddr = downstream->gateway_address.s_addr; + m->header.yiaddr = 0x12345678; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_ACK)); + m->header.ciaddr = 0x12345678; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.ciaddr = 0; + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 200)); + m->header.op = BOOTREQUEST; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREPLY; + + /* Invalid message (NULL giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 201)); + m->header.giaddr = INADDR_ANY; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + /* Invalid message (unexpected giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 202)); + m->header.giaddr = 1234567; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_NAK)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(sd_event_loop(e)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); From 0ff6bace599c366b8f5b7d9f53c2a1e4b36909ab Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 2 May 2026 23:32:33 +0900 Subject: [PATCH 3/9] fuzz: introduce fuzzer for DHCP relay agent --- src/libsystemd-network/fuzz-dhcp-relay.c | 61 +++++++++++++++++++++++ src/libsystemd-network/meson.build | 3 ++ test/fuzz/fuzz-dhcp-relay/discover | Bin 0 -> 300 bytes test/fuzz/fuzz-dhcp-relay/offer | Bin 0 -> 300 bytes 4 files changed, 64 insertions(+) create mode 100644 src/libsystemd-network/fuzz-dhcp-relay.c create mode 100644 test/fuzz/fuzz-dhcp-relay/discover create mode 100644 test/fuzz/fuzz-dhcp-relay/offer diff --git a/src/libsystemd-network/fuzz-dhcp-relay.c b/src/libsystemd-network/fuzz-dhcp-relay.c new file mode 100644 index 0000000000..2c828b9548 --- /dev/null +++ b/src/libsystemd-network/fuzz-dhcp-relay.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-relay-internal.h" +#include "fd-util.h" +#include "fuzz.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "tests.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + union in_addr_union a; + + fuzz_setup_logging(); + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + (void) downstream_process_message(downstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + (void) upstream_process_message(upstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + + return 0; +} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 7693687a13..5d2368cc98 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -136,6 +136,9 @@ executables += [ network_fuzz_template + { 'sources' : files('fuzz-dhcp-client.c'), }, + network_fuzz_template + { + 'sources' : files('fuzz-dhcp-relay.c'), + }, network_fuzz_template + { 'sources' : files('fuzz-dhcp6-client.c'), }, diff --git a/test/fuzz/fuzz-dhcp-relay/discover b/test/fuzz/fuzz-dhcp-relay/discover new file mode 100644 index 0000000000000000000000000000000000000000..4ffce3951fef0a04a199331a762fb2b62939a397 GIT binary patch literal 300 ncmZQ%WMhz75 Date: Sun, 3 May 2026 09:56:45 +0900 Subject: [PATCH 4/9] network: re-implement DHCP relay support with new sd-dhcp-relay --- src/network/meson.build | 1 + src/network/networkd-address.c | 7 + src/network/networkd-conf.c | 1 + src/network/networkd-dhcp-relay.c | 362 +++++++++++++++++++++++ src/network/networkd-dhcp-relay.h | 19 ++ src/network/networkd-gperf.gperf | 4 + src/network/networkd-link.c | 22 ++ src/network/networkd-link.h | 1 + src/network/networkd-manager.c | 7 + src/network/networkd-manager.h | 8 + src/network/networkd-network-gperf.gperf | 7 + src/network/networkd-network.c | 10 + src/network/networkd-network.h | 11 + src/network/networkd-queue.c | 1 + src/network/networkd-queue.h | 1 + 15 files changed, 462 insertions(+) create mode 100644 src/network/networkd-dhcp-relay.c create mode 100644 src/network/networkd-dhcp-relay.h diff --git a/src/network/meson.build b/src/network/meson.build index 9844229ebd..110af9511c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -43,6 +43,7 @@ systemd_networkd_extract_sources = files( 'networkd-conf.c', 'networkd-dhcp-common.c', 'networkd-dhcp-prefix-delegation.c', + 'networkd-dhcp-relay.c', 'networkd-dhcp-server-bus.c', 'networkd-dhcp-server-static-lease.c', 'networkd-dhcp-server.c', diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 193c4a04b5..9737ae0288 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -19,6 +19,7 @@ #include "networkd-address.h" #include "networkd-address-pool.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-link.h" @@ -245,6 +246,9 @@ static Address* address_detach_impl(Address *address) { if (address->network->dhcp_server_address == address) address->network->dhcp_server_address = NULL; + if (address->network->dhcp_relay_agent_address == address) + address->network->dhcp_relay_agent_address = NULL; + address->network = NULL; return address; } @@ -893,6 +897,8 @@ static int address_drop(Address *in, bool removed_by_us) { ipv4acd_detach(link, address); + (void) link_dhcp_relay_address_dropped(link, address); + address_detach(address); if (!removed_by_us) { @@ -2485,5 +2491,6 @@ int network_drop_invalid_addresses(Network *network) { if (r < 0) return r; + network_adjust_dhcp_relay(network); return 0; } diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index bce16284f8..ba3e2f5a43 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -22,6 +22,7 @@ int manager_parse_config_file(Manager *m) { "DHCPv4\0" "DHCPv6\0" "DHCPServer\0" + "DHCPRelay\0" "DHCP\0", config_item_perf_lookup, networkd_gperf_lookup, CONFIG_PARSE_WARN, diff --git a/src/network/networkd-dhcp-relay.c b/src/network/networkd-dhcp-relay.c new file mode 100644 index 0000000000..5e793a0a12 --- /dev/null +++ b/src/network/networkd-dhcp-relay.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" +#include "sd-id128.h" + +#include "conf-parser.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "networkd-address.h" +#include "networkd-dhcp-relay.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-queue.h" +#include "string-table.h" +#include "string-util.h" /* IWYU pragma: keep */ + +#define DHCP_RELAY_APP_REMOTE_ID SD_ID128_MAKE(85,bb,eb,d2,b8,56,47,0b,b0,86,4c,f3,d3,9b,c1,b5) + +void network_adjust_dhcp_relay(Network *network) { + assert(network); + assert(network->manager); + + if (network->dhcp_relay_interface_mode < 0) + return; + + if (network->bond) { + log_warning("%s: DHCPRelay= is enabled for bond slave. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (network->dhcp_server) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPServer= is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPv4 client is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (in4_addr_is_null(&network->manager->dhcp_relay_server_address)) { + log_warning("%s: DHCPRelay= is enabled, but [DHCPRelay] ServerAddress= in networkd.conf is not configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + Address *a; + ORDERED_HASHMAP_FOREACH(a, network->addresses_by_section) { + assert(!section_is_invalid(a->section)); + + if (a->family != AF_INET) + continue; + + if (in4_addr_is_set(&a->in_addr_peer.in)) + continue; + + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) { + if (!in4_addr_equal(&a->in_addr.in, &network->dhcp_relay_agent_address_in_addr)) + continue; + + } else { + if (in4_addr_is_localhost(&a->in_addr.in)) + continue; + + if (in4_addr_is_link_local(&a->in_addr.in)) + continue; + + if (a->scope != RT_SCOPE_UNIVERSE) + continue; + } + + network->dhcp_relay_agent_address = a; + break; + } + + if (!network->dhcp_relay_agent_address) { + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + log_warning("%s: Configured AgentAddress=%s not found among static addresses. Disabling DHCP relay agent.", + network->filename, IN4_ADDR_TO_STRING(&network->dhcp_relay_agent_address_in_addr)); + else + log_warning("%s: DHCPRelay= is enabled, but no suitable static address configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } +} + +static int manager_configure_dhcp_relay(Manager *manager) { + int r; + + assert(manager); + assert(manager->event); + + if (manager->dhcp_relay) + return 0; + + if (in4_addr_is_null(&manager->dhcp_relay_server_address)) + return -EADDRNOTAVAIL; + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + r = sd_dhcp_relay_new(&relay); + if (r < 0) + return r; + + r = sd_dhcp_relay_attach_event(relay, manager->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_server_address(relay, &manager->dhcp_relay_server_address); + if (r < 0) + return r; + + if (iovec_is_set(&manager->dhcp_relay_remote_id)) { + r = sd_dhcp_relay_set_remote_id(relay, &manager->dhcp_relay_remote_id); + if (r < 0) + return r; + } else { + sd_id128_t id; + r = sd_id128_get_machine_app_specific(DHCP_RELAY_APP_REMOTE_ID, &id); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING(SD_ID128_TO_STRING(id))); + if (r < 0) + return r; + } + + r = sd_dhcp_relay_set_server_identifier_override(relay, manager->dhcp_relay_override_server_id); + if (r < 0) + return r; + + r = dhcp_relay_set_extra_options(relay, &manager->dhcp_relay_extra_options); + if (r < 0) + return r; + + manager->dhcp_relay = TAKE_PTR(relay); + return 0; +} + +static int link_configure_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + assert(link->network); + assert(!link->dhcp_relay_interface); + assert(link->network->dhcp_relay_agent_address); + assert(link->network->dhcp_relay_interface_mode >= 0 && link->network->dhcp_relay_interface_mode < _DHCP_RELAY_INTERFACE_MAX); + + r = manager_configure_dhcp_relay(link->manager); + if (r < 0) + return r; + + bool upstream = link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_UPSTREAM; + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = NULL; + r = sd_dhcp_relay_add_interface(link->manager->dhcp_relay, link->ifindex, upstream, &interface); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_ifname(interface, link->ifname); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_address( + interface, + &link->network->dhcp_relay_agent_address->in_addr.in, + link->network->dhcp_relay_agent_address->prefixlen); + if (r < 0) + return r; + + if (upstream) { + r = sd_dhcp_relay_upstream_set_priority(interface, link->network->dhcp_relay_interface_priority); + if (r < 0) + return r; + } else { + if (in4_addr_is_set(&link->network->dhcp_relay_gateway_address)) + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_gateway_address); + else + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_agent_address->in_addr.in); + if (r < 0) + return r; + + if (iovec_is_set(&link->network->dhcp_relay_circuit_id)) + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &link->network->dhcp_relay_circuit_id); + else + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &IOVEC_MAKE_STRING(link->ifname)); + if (r < 0) + return r; + + r = sd_dhcp_relay_downstream_set_virtual_subnet_selection(interface, &link->network->dhcp_relay_vss); + if (r < 0) + return r; + + r = downstream_set_extra_options(interface, &link->network->dhcp_relay_extra_options); + if (r < 0) + return r; + } + + link->dhcp_relay_interface = TAKE_PTR(interface); + return 0; +} + +static bool dhcp_relay_is_ready_to_configure(Link *link) { + assert(link); + assert(link->network); + + if (!link_is_ready_to_configure(link, /* allow_unmanaged= */ false)) + return false; + + if (!link_has_carrier(link)) + return false; + + if (!link->static_addresses_configured) + return false; + + Address *a; + if (address_get(link, link->network->dhcp_relay_agent_address, &a) < 0) + return false; + + if (!address_is_ready(a)) + return false; + + return true; +} + +static int dhcp_relay_process_request(Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + if (!dhcp_relay_is_ready_to_configure(link)) + return 0; + + r = link_configure_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure DHCP relay agent: %m"); + + log_link_debug(link, "DHCP relay agent is configured."); + + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start DHCP relay agent: %m"); + + return 1; +} + +int link_request_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (link->network->dhcp_relay_interface_mode < 0) + return 0; + + if (link->dhcp_relay_interface) + return 0; + + r = link_queue_request(link, REQUEST_TYPE_DHCP_RELAY, dhcp_relay_process_request, NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request configuring of the DHCP relay agent: %m"); + + log_link_debug(link, "Requested configuring of the DHCP relay agent."); + return 0; +} + +int link_start_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_relay_interface) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface); + if (r < 0) + return r; + + log_link_debug(link, "Relaying DHCPv4 messages."); + return 0; +} + +int link_dhcp_relay_address_dropped(Link *link, const Address *address) { + int r; + + assert(link); + assert(link->manager); + assert(address); + + /* This is called when an address is removed from the interface. */ + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (!link->dhcp_relay_interface) + return 0; + + if (address->family != AF_INET) + return 0; + + struct in_addr a; + uint8_t prefixlen; + r = sd_dhcp_relay_interface_get_address(link->dhcp_relay_interface, &a, &prefixlen); + if (r <= 0) + return r; + + if (!in4_addr_equal(&address->in_addr.in, &a)) + return 0; + + if (address->prefixlen != prefixlen) + return 0; + + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + return r; + + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + /* The address may be reconfigured later. Let's reconfigure DHCP relay interface when the address comes back. */ + return link_request_dhcp_relay(link); +} + +static const char * const dhcp_relay_interface_mode_table[_DHCP_RELAY_INTERFACE_MAX] = { + [DHCP_RELAY_INTERFACE_UPSTREAM] = "upstream", + [DHCP_RELAY_INTERFACE_DOWNSTREAM] = "downstream", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_relay_interface_mode, DHCPRelayInterfaceMode); + +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT( + config_parse_dhcp_relay_interface_mode, + dhcp_relay_interface_mode, + DHCPRelayInterfaceMode, + _DHCP_RELAY_INTERFACE_INVALID); diff --git a/src/network/networkd-dhcp-relay.h b/src/network/networkd-dhcp-relay.h new file mode 100644 index 0000000000..7d56740ede --- /dev/null +++ b/src/network/networkd-dhcp-relay.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-forward.h" + +typedef enum DHCPRelayInterfaceMode { + DHCP_RELAY_INTERFACE_UPSTREAM, + DHCP_RELAY_INTERFACE_DOWNSTREAM, + _DHCP_RELAY_INTERFACE_MAX, + _DHCP_RELAY_INTERFACE_INVALID = -EINVAL, +} DHCPRelayInterfaceMode; + +void network_adjust_dhcp_relay(Network *network); + +int link_request_dhcp_relay(Link *link); +int link_start_dhcp_relay(Link *link); +int link_dhcp_relay_address_dropped(Link *link, const Address *address); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_relay_interface_mode); diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 3684a696ff..242da72c27 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -45,6 +45,10 @@ DHCPv6.UseDomains, config_parse_use_domains, DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid) DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Manager, dhcp_server_persist_leases) +DHCPRelay.ServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Manager, dhcp_relay_server_address) +DHCPRelay.OverrideServerIdentifier, config_parse_bool, 0, offsetof(Manager, dhcp_relay_override_server_id) +DHCPRelay.RemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Manager, dhcp_relay_remote_id) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Manager, dhcp_relay_extra_options) /* Deprecated */ DHCP.DUIDType, config_parse_manager_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0 diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a69a5e8979..969818e9f7 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -10,6 +10,7 @@ #include "sd-bus.h" #include "sd-dhcp-client.h" +#include "sd-dhcp-relay.h" #include "sd-dhcp-server.h" #include "sd-dhcp6-client.h" #include "sd-dhcp6-lease.h" @@ -41,6 +42,7 @@ #include "networkd-bridge-mdb.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -240,6 +242,7 @@ static void link_free_engines(Link *link) { if (!link) return; + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); @@ -424,6 +427,10 @@ int link_stop_engines(Link *link, bool may_keep_dynamic) { ndisc_flush(link); } + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent: %m")); + r = sd_dhcp_server_stop(link->dhcp_server); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); @@ -739,6 +746,10 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) { log_link_debug(link, "Acquiring IPv4 link-local address."); } + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not start DHCP relay agent: %m"); + r = link_start_dhcp4_server(link); if (r < 0) return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); @@ -1156,6 +1167,7 @@ static int link_drop_dynamic_config(Link *link, Network *network) { RET_GATHER(r, link_drop_dhcp4_config(link, network)); RET_GATHER(r, link_drop_dhcp6_config(link, network)); RET_GATHER(r, link_drop_dhcp_pd_config(link, network)); + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */ link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx); @@ -1274,6 +1286,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_request_dhcp_relay(link); + if (r < 0) + return r; + r = link_request_dhcp_server(link); if (r < 0) return r; @@ -2605,6 +2621,12 @@ static int link_update_name(Link *link, sd_netlink_message *message) { return log_link_debug_errno(link, r, "Failed to update interface name in NDisc: %m"); } + if (link->dhcp_relay_interface) { + r = sd_dhcp_relay_interface_set_ifname(link->dhcp_relay_interface, link->ifname); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to update interface name in DHCP relay interface: %m"); + } + if (link->dhcp_server) { r = sd_dhcp_server_set_ifname(link->dhcp_server, link->ifname); if (r < 0) diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 456ec99185..4c3e53b93e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -143,6 +143,7 @@ typedef struct Link { bool bridge_vlan_set:1; bool bearer_configured:1; + sd_dhcp_relay_interface *dhcp_relay_interface; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index f43709da35..63ba0c166f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -5,6 +5,7 @@ #include #include "sd-bus.h" +#include "sd-dhcp-relay.h" #include "sd-event.h" #include "sd-netlink.h" #include "sd-resolve.h" @@ -25,6 +26,7 @@ #include "errno-util.h" #include "fd-util.h" #include "initrd-util.h" +#include "iovec-util.h" #include "mount-util.h" #include "netlink-internal.h" #include "netlink-util.h" @@ -701,6 +703,7 @@ int manager_new(Manager **ret, bool test_mode) { .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, .duid_product_uuid.type = DUID_TYPE_UUID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_server_persist_leases = DHCP_SERVER_PERSIST_LEASES_YES, .serialization_fd = -EBADF, .ip_forwarding = { -1, -1, }, @@ -760,6 +763,10 @@ Manager* manager_free(Manager *m) { sd_netlink_unref(m->nfnl); sd_resolve_unref(m->resolve); + iovec_done(&m->dhcp_relay_remote_id); + tlv_done(&m->dhcp_relay_extra_options); + sd_dhcp_relay_unref(m->dhcp_relay); + m->routes = set_free(m->routes); m->nexthops_by_id = hashmap_free(m->nexthops_by_id); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index bacf3df444..2e1b643090 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -3,6 +3,7 @@ #include "networkd-forward.h" #include "networkd-network.h" +#include "tlv-util.h" typedef enum ManagerState { MANAGER_RUNNING, @@ -74,6 +75,13 @@ typedef struct Manager { bool has_product_uuid; bool product_uuid_requested; + /* DHCP relay agent */ + sd_dhcp_relay *dhcp_relay; + struct in_addr dhcp_relay_server_address; + bool dhcp_relay_override_server_id; + struct iovec dhcp_relay_remote_id; + TLV dhcp_relay_extra_options; + char* dynamic_hostname; char* dynamic_timezone; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 9e6eb7c799..4b54c6d88d 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -132,6 +132,7 @@ Network.VLAN, config_parse_stacked_netdev, Network.VXLAN, config_parse_stacked_netdev, NETDEV_KIND_VXLAN, offsetof(Network, stacked_netdev_names) Network.Xfrm, config_parse_stacked_netdev, NETDEV_KIND_XFRM, offsetof(Network, stacked_netdev_names) Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp) +Network.DHCPRelay, config_parse_dhcp_relay_interface_mode, 0, offsetof(Network, dhcp_relay_interface_mode) Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server) Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local) Network.IPv6LinkLocalAddressGenerationMode, config_parse_ipv6_link_local_address_gen_mode, 0, offsetof(Network, ipv6ll_address_gen_mode) @@ -360,6 +361,12 @@ IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens) IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel) IPv6AcceptRA.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, ndisc_nft_set_context) +DHCPRelay.AgentAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_agent_address_in_addr) +DHCPRelay.GatewayAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_gateway_address) +DHCPRelay.CircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) +DHCPRelay.VirtualSubnetSelection, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_vss) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_relay_extra_options) +DHCPRelay.InterfacePriority, config_parse_int, 0, offsetof(Network, dhcp_relay_interface_priority) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index dfe524d150..b1569866d1 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -7,6 +7,7 @@ #include "conf-files.h" #include "conf-parser.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "net-condition.h" #include "netdev/macvlan.h" #include "netif-sriov.h" @@ -433,6 +434,9 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_pd_subnet_id = -1, .dhcp_pd_route_metric = DHCP6PD_ROUTE_METRIC, + .dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), + .dhcp_server_bind_to_interface = true, .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, @@ -547,6 +551,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "DHCPv6\0" "DHCPv6PrefixDelegation\0" /* compat */ "DHCPPrefixDelegation\0" + "DHCPRelay\0" "DHCPServer\0" "DHCPServerStaticLease\0" "IPv6AcceptRA\0" @@ -767,6 +772,11 @@ static Network *network_free(Network *network) { ordered_set_free(network->route_domains); set_free(network->dnssec_negative_trust_anchors); + /* DHCP relay agent */ + iovec_done(&network->dhcp_relay_circuit_id); + iovec_done(&network->dhcp_relay_vss); + tlv_done(&network->dhcp_relay_extra_options); + /* DHCP server */ free(network->dhcp_server_relay_agent_circuit_id); free(network->dhcp_server_relay_agent_remote_id); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 625b3b9a08..d9e516ff4e 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -12,6 +12,7 @@ #include "network-util.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-common.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -208,6 +209,16 @@ typedef struct Network { uint32_t dhcp6_route_table; bool dhcp6_route_table_set; + /* DHCP Relay Agent Support */ + DHCPRelayInterfaceMode dhcp_relay_interface_mode; + Address *dhcp_relay_agent_address; + struct in_addr dhcp_relay_agent_address_in_addr; + struct in_addr dhcp_relay_gateway_address; + struct iovec dhcp_relay_circuit_id; + struct iovec dhcp_relay_vss; + TLV dhcp_relay_extra_options; + int dhcp_relay_interface_priority; + /* DHCP Server Support */ bool dhcp_server; bool dhcp_server_bind_to_interface; diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 0ab372b4b2..ea846327b0 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -365,6 +365,7 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { [REQUEST_TYPE_ADDRESS_LABEL] = "address label", [REQUEST_TYPE_BRIDGE_FDB] = "bridge FDB", [REQUEST_TYPE_BRIDGE_MDB] = "bridge MDB", + [REQUEST_TYPE_DHCP_RELAY] = "DHCP relay agent", [REQUEST_TYPE_DHCP_SERVER] = "DHCP server", [REQUEST_TYPE_DHCP4_CLIENT] = "DHCPv4 client", [REQUEST_TYPE_DHCP6_CLIENT] = "DHCPv6 client", diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index d656d7aa7a..70c8b2ca9e 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -13,6 +13,7 @@ typedef enum RequestType { REQUEST_TYPE_ADDRESS_LABEL, REQUEST_TYPE_BRIDGE_FDB, REQUEST_TYPE_BRIDGE_MDB, + REQUEST_TYPE_DHCP_RELAY, REQUEST_TYPE_DHCP_SERVER, REQUEST_TYPE_DHCP4_CLIENT, REQUEST_TYPE_DHCP6_CLIENT, From 08b3ebf5f1d7a22f29b6e3dbe8ab6c9ac8407084 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 11 May 2026 07:48:27 +0900 Subject: [PATCH 5/9] test-network: add test case for DHCP relay --- .../conf/25-dhcp-relay-downstream.network | 14 +++ .../conf/25-dhcp-relay-upstream.network | 12 ++ test/test-network/conf/25-dhcp-relay.conf | 5 + test/test-network/systemd-networkd-tests.py | 110 ++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 test/test-network/conf/25-dhcp-relay-downstream.network create mode 100644 test/test-network/conf/25-dhcp-relay-upstream.network create mode 100644 test/test-network/conf/25-dhcp-relay.conf diff --git a/test/test-network/conf/25-dhcp-relay-downstream.network b/test/test-network/conf/25-dhcp-relay-downstream.network new file mode 100644 index 0000000000..49ff765e95 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-downstream.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-down + +[Network] +Address=198.51.100.10/24 +IPv6AcceptRA=no +DHCPRelay=downstream + +[DHCPRelay] +AgentAddress=198.51.100.10 +GatewayAddress=198.51.100.10 +CircuitId=string:test-dhcp-relay-circuit-id +VirtualSubnetSelection=string:subnet-hoge diff --git a/test/test-network/conf/25-dhcp-relay-upstream.network b/test/test-network/conf/25-dhcp-relay-upstream.network new file mode 100644 index 0000000000..d84f6a71fe --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-upstream.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-up + +[Network] +Address=192.0.2.10/24 +IPv6AcceptRA=no +DHCPRelay=upstream + +[DHCPRelay] +AgentAddress=192.0.2.10 +InterfacePriority=100 diff --git a/test/test-network/conf/25-dhcp-relay.conf b/test/test-network/conf/25-dhcp-relay.conf new file mode 100644 index 0000000000..5d6bcb2c4a --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[DHCPRelay] +ServerAddress=192.0.2.1 +OverrideServerIdentifier=yes +RemoteId=string:test-dhcp-relay-remote-id diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 8609a63916..28c418460c 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1222,6 +1222,7 @@ def tear_down_common(): # 3. remove network namespace call_quiet('ip netns del ns99') call_quiet('ip netns del ns-bridge') + call_quiet('ip netns del ns-relay') call_quiet('ip netns del ns-server') # 4. remove links @@ -8393,6 +8394,115 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): # For issue #30763. self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') + def test_sd_dhcp_relay(self): + check_output('ip netns add ns-relay') + check_output('ip netns add ns-server') + + check_output('ip link add relay-down type veth peer client') + check_output('ip link set relay-down netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-down up') + + check_output('ip link add relay-up type veth peer server') + check_output('ip link set relay-up netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-up up') + + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + with tempfile.TemporaryDirectory() as tmp: + conf_dir = pathlib.Path(tmp) / 'run/systemd/networkd.conf.d' + unit_dir = pathlib.Path(tmp) / 'run/systemd/network' + netif_dir = pathlib.Path(tmp) / 'run/systemd/netif' + mkdir_p(conf_dir) + mkdir_p(unit_dir) + mkdir_p(netif_dir) + + # Do not use shutil.chown(), as it fails when running with sanitizers. + check_output(f'chown systemd-network:systemd-network {netif_dir}') + + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay.conf', conf_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-downstream.network', unit_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-upstream.network', unit_dir) + + cmd = [ + 'systemd-run', + '--unit=networkd-test-dhcp-relay.service', + '--service-type=notify-reload', + '-p', 'User=systemd-network', + '-p', 'FileDescriptorStoreMax=512', + '-p', 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', 'CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', f'BindPaths={conf_dir}:/run/systemd/networkd.conf.d', + '-p', f'BindPaths={unit_dir}:/run/systemd/network', + '-p', f'BindPaths={netif_dir}:/run/systemd/netif', + '-p', 'TemporaryFileSystem=/run/dbus', + '-p', 'TemporaryFileSystem=/run/systemd/report', + '-p', 'TemporaryFileSystem=/run/systemd/resolve.hook', + '-p', 'TemporaryFileSystem=/var/lib/systemd/network', + '-p', 'TemporaryFileSystem=/etc/systemd/network', + '-p', 'TemporaryFileSystem=/usr/lib/systemd/network', + '-p', 'ReadOnlyPaths=/sys', + '-p', 'NetworkNamespacePath=/run/netns/ns-relay', + ] # fmt: skip + if enable_debug: + cmd += ['-p', 'Environment=SYSTEMD_LOG_LEVEL=debug'] + if asan_options: + cmd += ['-p', f'Environment=ASAN_OPTIONS={asan_options}'] + if lsan_options: + cmd += ['-p', f'Environment=LSAN_OPTIONS={lsan_options}'] + if ubsan_options: + cmd += ['-p', f'Environment=UBSAN_OPTIONS={ubsan_options}'] + if asan_options or lsan_options or ubsan_options: + cmd += ['-p', 'TimeoutStopFailureMode=abort'] + + cmd += [networkd_bin] + + try: + check_output(*cmd) + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + + copy_network_unit('25-dhcp-client-simple.network') + start_networkd() + self.wait_online('client:routable') + + print('## ip -4 address show dev client scope global') + output = check_output('ip -4 address show dev client scope global') + print(output) + self.assertRegex(output, r'198\.51\.100\.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + # fmt: off + self.assertRegex(output, r'default via 198\.51\.100\.10 proto dhcp src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.0/24 proto kernel scope link src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.10 proto dhcp scope link src 198\.51\.100\.10[0-9]') + # fmt: on + + print('## dnsmasq log') + output = read_dnsmasq_log_file() + print(output) + # The option 82 is logged as agent-info since dnsmasq-2.92, otherwise logged as agent-id. + self.assertRegex(output, r'option: 82 (agent-id|agent-info)') + + print('## journal of networkd-test-dhcp-relay.service') + check_output('journalctl --sync') + output = check_output('journalctl --no-hostname --output=short-monotonic --unit=networkd-test-dhcp-relay.service -I') # fmt: skip + self.assertIn('relay-down: DHCPv4 relay: Received BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Forwarded BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Received BOOTREPLY', output) + self.assertIn('relay-down: DHCPv4 relay: Forwarded BOOTREPLY', output) + finally: + call_quiet('systemctl stop networkd-test-dhcp-relay.service') + call_quiet('systemctl reset-failed networkd-test-dhcp-relay.service') + class NetworkdDHCPClientTests(unittest.TestCase, Utilities): def setUp(self): From aa063e593ba0d34d86a638df8985632670b269a2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 11 May 2026 08:56:40 +0900 Subject: [PATCH 6/9] man: document DHCP relay configuration --- man/networkd.conf.xml | 61 +++++++++++++++++++++ man/systemd.network.xml | 118 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml index f611e244f5..8dd1181508 100644 --- a/man/networkd.conf.xml +++ b/man/networkd.conf.xml @@ -393,6 +393,67 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + + [DHCPRelay] Section Options + The [DHCPRelay] section contains host-wide settings for the DHCP relay agent. + + + + + ServerAddress= + + Specifies the IPv4 address of the upstream DHCP server. This is required for the host to act + as a DHCP relay agent. + + + + + + + OverrideServerIdentifier= + + Takes a boolean value. When enabled, the Server Identifier Override sub-option and the Relay + Agent Flags sub-option in the Relay Agent Information option will be appended to the DHCP message + forwarded to the upstream DHCP server. Defaults to false. + + + + + + + RemoteId= + + Specifies the remote ID, which is typically used by DHCP servers to identify the DHCP relay + agent. Takes a data type and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + If unset, a UUID generated from the local machine ID will be used. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See RemoteId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. Defaults to unset, and no extra sub-option will be appended. + + + + + + + + [DHCPServer] Section Options diff --git a/man/systemd.network.xml b/man/systemd.network.xml index d62e4d6232..fcb700bff0 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -435,6 +435,26 @@ + + DHCPRelay= + + Takes one of upstream or downstream. When specified, + the host acts as a DHCP relay agent. When set to upstream, the interface behaves + as an upstream interface of the DHCP relay agent. When set to downstream, the + interface behaves as a downstream interface of the DHCP relay agent. To make the DHCP relay agent + work, at least one upstream interface and one downstream interface must be configured on the host. + This requires ServerAddress= in the [DHCPRelay] section to be configured in + networkd.conf. If ServerAddress= is not configured, this + setting is ignored. See + networkd.conf5 + for more details about ServerAddress= and other host-wide settings. + Also, further per-interface settings can be configured in the [DHCPRelay] section described below. + Defaults to unset, and the interface is not used for DHCP relay forwarding. + + + + + DHCPServer= @@ -3868,6 +3888,104 @@ Token=prefixstable:2002:da8:1:: + + [DHCPRelay] Section Options + The [DHCPRelay] section contains per-interface settings for the DHCP relay agent. The settings in + this section are only used when DHCPRelay= in the [Network] section is configured. + + + + + + AgentAddress= + + Takes an IPv4 address. The specified address is used as the source IP address of packets + forwarded to the upstream DHCP server. The address may also be used for the Server Identifier + Override sub-option and the Link Selection sub-option in the Relay Agent Information option + appended to DHCP messages forwarded to the upstream DHCP server. This setting applies to both + upstream and downstream interfaces. Defaults to unset. If unset, a statically configured IPv4 + address in the .network file is selected automatically. When multiple static IPv4 addresses are + configured, it is recommended to specify this setting explicitly. + + + + + + + GatewayAddress= + + Takes an IPv4 address. The specified address is set to the giaddr field of + the DHCP message header when the DHCP message is forwarded to the upstream DHCP server. This is + used only when DHCPRelay=downstream, and ignored otherwise. Defaults to unset, + and the same address specified with AgentAddress= will be used. + + + + + + + CircuitId= + + Specifies the circuit ID of the downstream interface. This is set as the Circuit ID + sub-option in the Relay Agent Information option in the forwarded DHCP message. Takes a data type + and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + This is used only when DHCPRelay=downstream, and ignored otherwise. If unset, + the name of the interface will be used. + + + + + + + VirtualSubnetSelection= + + Specifies a unique identifier used by the DHCP server to select the downstream subnet + independently of giaddr. This value is used as the Virtual Subnet Selection sub-option in the Relay + Agent Information option in the forwarded DHCP message. Takes a value in the same format as + CircuitId=. This is used only when DHCPRelay=downstream, and + ignored otherwise. Defaults to unset, and the sub-option is not set. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See CircuitId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. This is used only when + DHCPRelay=downstream, and ignored otherwise. Defaults to unset, and no extra + sub-option will be appended. + + + + + + + InterfacePriority= + + Specifies the priority of the upstream interface. Takes an integer value. When the host has + multiple upstream interfaces, the upstream interface with the highest (largest) priority will be + used for forwarding DHCP messages to the upstream DHCP server. When multiple interfaces have the + same priority, which interface is used is unspecified. This is used only when + DHCPRelay=upstream, and ignored otherwise. Defaults to zero. + + + + + + + [DHCPServer] Section Options The [DHCPServer] section contains settings for the DHCP server, if enabled via the From cb2bba6b040ee11da822f06b0cb23a47444671e2 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 08:29:51 +0900 Subject: [PATCH 7/9] network: drop use of legacy DHCP relay agent in sd-dhcp-server Then, this deprecates - BindToInterface= - RelayTarget= - RelayAgentCircuitId= - RelayAgentRemoteId= settings in [DHCPServer] section. These are gracefully translated as new settings. --- man/systemd.network.xml | 56 +------------- src/network/networkd-dhcp-relay.c | 18 +++++ src/network/networkd-dhcp-relay.h | 1 + src/network/networkd-dhcp-server-bus.c | 17 +---- src/network/networkd-dhcp-server.c | 75 +++++++------------ src/network/networkd-dhcp-server.h | 1 - src/network/networkd-link-bus.c | 3 +- src/network/networkd-link.c | 6 ++ src/network/networkd-link.h | 1 + src/network/networkd-network-gperf.gperf | 8 +- src/network/networkd-network.c | 4 +- src/network/networkd-network.h | 6 +- .../conf/25-agent-client-peer.network | 5 +- .../test-network/conf/25-agent-client.network | 1 - .../conf/25-agent-server-peer.network | 3 +- .../test-network/conf/25-agent-server.network | 16 ---- .../conf/25-agent-veth-server.netdev | 9 --- test/test-network/systemd-networkd-tests.py | 19 ++++- 18 files changed, 85 insertions(+), 164 deletions(-) delete mode 100644 test/test-network/conf/25-agent-server.network delete mode 100644 test/test-network/conf/25-agent-veth-server.netdev diff --git a/man/systemd.network.xml b/man/systemd.network.xml index fcb700bff0..dc97e17838 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -464,10 +464,9 @@ Even if this is enabled, the DHCP server will not be started automatically and wait for the persistent storage being ready to load/save leases in the storage, unless - RelayTarget= or PersistLeases=no/runtime are specified in the - [DHCPServer] section. It will be started after - systemd-networkd-persistent-storage.service is started, which calls - networkctl persistent-storage yes. See + PersistLeases=no/runtime is specified in the [DHCPServer] section. It will be + started after systemd-networkd-persistent-storage.service is started, which + calls networkctl persistent-storage yes. See networkctl1 for more details. @@ -4279,52 +4278,6 @@ ServerAddress=192.168.0.1/24 - - BindToInterface= - - Takes a boolean value. When yes, DHCP server socket will be bound - to its network interface and all socket communication will be restricted to this interface. - Defaults to yes, except if RelayTarget= is used (see below), - in which case it defaults to no. - - - - - - RelayTarget= - - Takes an IPv4 address, which must be in the format described in - inet_pton3. - Turns this DHCP server into a DHCP relay agent. See RFC 1542. - The address is the address of DHCP server or another relay agent to forward DHCP messages to and from. - - - - - - RelayAgentCircuitId= - - Specifies value for Agent Circuit ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Circuit ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - - - RelayAgentRemoteId= - - Specifies value for Agent Remote ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Remote ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - RapidCommit= @@ -4356,9 +4309,6 @@ ServerAddress=192.168.0.1/24 networkd.conf5, which defaults to yes, will be used. - When RelayTarget= is specified, this setting will be ignored and no leases - will be saved, as there will be no bound lease on the server. - diff --git a/src/network/networkd-dhcp-relay.c b/src/network/networkd-dhcp-relay.c index 5e793a0a12..7811d9d585 100644 --- a/src/network/networkd-dhcp-relay.c +++ b/src/network/networkd-dhcp-relay.c @@ -209,6 +209,18 @@ static int link_configure_dhcp_relay(Link *link) { } link->dhcp_relay_interface = TAKE_PTR(interface); + + if (link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_COMPAT && + !link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_add_interface( + link->manager->dhcp_relay, + DHCP_RELAY_IFINDEX_UNBOUND, + /* is_upstream= */ true, + &link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + return 0; } @@ -298,6 +310,12 @@ int link_start_dhcp_relay(Link *link) { if (r < 0) return r; + if (link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + log_link_debug(link, "Relaying DHCPv4 messages."); return 0; } diff --git a/src/network/networkd-dhcp-relay.h b/src/network/networkd-dhcp-relay.h index 7d56740ede..2a676931bd 100644 --- a/src/network/networkd-dhcp-relay.h +++ b/src/network/networkd-dhcp-relay.h @@ -6,6 +6,7 @@ typedef enum DHCPRelayInterfaceMode { DHCP_RELAY_INTERFACE_UPSTREAM, DHCP_RELAY_INTERFACE_DOWNSTREAM, + DHCP_RELAY_INTERFACE_COMPAT, _DHCP_RELAY_INTERFACE_MAX, _DHCP_RELAY_INTERFACE_INVALID = -EINVAL, } DHCPRelayInterfaceMode; diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index e5ee6d3f6d..58e0a26f22 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -30,9 +30,6 @@ static int property_get_leases( if (!s) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname); - if (sd_dhcp_server_is_in_relay_mode(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has DHCP relay agent active.", l->ifname); - r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)"); if (r < 0) return r; @@ -82,15 +79,12 @@ static int property_get_pool_size( sd_bus_message *reply, void *userdata, sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); - sd_dhcp_server *s; - uint32_t v; assert(reply); - s = l->dhcp_server; - v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_size : UINT32_MAX; - + uint32_t v = l->dhcp_server ? l->dhcp_server->pool_size : UINT32_MAX; return sd_bus_message_append_basic(reply, 'u', &v); } @@ -102,15 +96,12 @@ static int property_get_pool_offset( sd_bus_message *reply, void *userdata, sd_bus_error *error) { + Link *l = ASSERT_PTR(userdata); - sd_dhcp_server *s; - uint32_t v; assert(reply); - s = l->dhcp_server; - v = s && !sd_dhcp_server_is_in_relay_mode(s) ? s->pool_offset : UINT32_MAX; - + uint32_t v = l->dhcp_server ? l->dhcp_server->pool_offset : UINT32_MAX; return sd_bus_message_append_basic(reply, 'u', &v); } diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 9903c6b9d3..2ee5a70abc 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -16,6 +16,7 @@ #include "fileio.h" #include "hashmap.h" #include "hostname-setup.h" +#include "iovec-util.h" #include "network-common.h" #include "networkd-address.h" #include "networkd-dhcp-server.h" @@ -66,6 +67,31 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } + if (in4_addr_is_set(&network->dhcp_relay_target_address)) { + if (!in4_addr_is_set(&network->manager->dhcp_relay_server_address)) { + /* [DHCPServer] RelayTarget= in .network file is replaced with + * [DHCPRelay] ServerAddress= in networkd.conf. */ + network->manager->dhcp_relay_server_address = network->dhcp_relay_target_address; + + /* [DHCPServer] RelayAgentRemoteId= in .network file is replaced with + * [DHCPRelay] RemoteId= in networkd.conf. */ + if (!iovec_is_set(&network->manager->dhcp_relay_remote_id)) { + iovec_done(&network->manager->dhcp_relay_remote_id); + network->manager->dhcp_relay_remote_id = TAKE_STRUCT(network->dhcp_relay_remote_id); + } + } + + /* Copy [DHCPServer] ServerAddress= to [DHCPRelay] AgentAddress= if unspecified. */ + if (!in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + network->dhcp_relay_agent_address_in_addr = network->dhcp_server_address_in_addr; + + /* Assume this interface acts as a downstream interface of the DHCP relay agent. Also, + * configure a catch-all upstream socket. */ + network->dhcp_relay_interface_mode = DHCP_RELAY_INTERFACE_COMPAT; + network->dhcp_server = false; + return 0; + } + assert(network->dhcp_server_address_prefixlen <= 32); if (network->dhcp_server_address_prefixlen == 0) { @@ -160,9 +186,6 @@ static DHCPServerPersistLeases link_get_dhcp_server_persist_leases(Link *link) { assert(link->manager); assert(link->network); - if (in4_addr_is_set(&link->network->dhcp_server_relay_target)) - return DHCP_SERVER_PERSIST_LEASES_NO; /* On relay mode. Nothing saved in the persistent storage. */ - if (link->network->dhcp_server_persist_leases >= 0) return link->network->dhcp_server_persist_leases; @@ -571,7 +594,6 @@ static int dhcp4_server_configure(Link *link) { DHCPStaticLease *static_lease; Link *uplink = NULL; Address *address; - bool bind_to_interface; int r; assert(link); @@ -681,19 +703,6 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set router address for DHCP server: %m"); } - r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m"); - - bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface; - r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m"); - - r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m"); - if (link->network->dhcp_server_emit_timezone) { _cleanup_free_ char *buffer = NULL; const char *tz = NULL; @@ -815,38 +824,6 @@ int link_request_dhcp_server(Link *link) { return 0; } -int config_parse_dhcp_server_relay_agent_suboption( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - char **suboption_value = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *suboption_value = mfree(*suboption_value); - return 0; - } - - const char *p = startswith(rvalue, "string:"); - if (!p) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue); - return 0; - } - return free_and_strdup(suboption_value, empty_to_null(p)); -} - int config_parse_dhcp_server_emit( const char *unit, const char *filename, diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index e46ad3a857..f60ca2d397 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -18,7 +18,6 @@ int link_request_dhcp_server(Link *link); int link_start_dhcp4_server(Link *link); void manager_toggle_dhcp4_server_state(Manager *manager, bool start); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ipv6_only_preferred); diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index a375d0c18b..53ac080a47 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -855,8 +855,7 @@ int link_object_find(sd_bus *bus, const char *path, const char *interface, void if (r < 0) return 0; - if (streq(interface, "org.freedesktop.network1.DHCPServer") && - (!link->dhcp_server || sd_dhcp_server_is_in_relay_mode(link->dhcp_server))) + if (streq(interface, "org.freedesktop.network1.DHCPServer") && !link->dhcp_server) return 0; if (streq(interface, "org.freedesktop.network1.DHCPv4Client") && !link->dhcp_client) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 969818e9f7..6d15e8deb4 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -243,6 +243,7 @@ static void link_free_engines(Link *link) { return; link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); @@ -431,6 +432,10 @@ int link_stop_engines(Link *link, bool may_keep_dynamic) { if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent: %m")); + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface_compat); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent (compat): %m")); + r = sd_dhcp_server_stop(link->dhcp_server); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); @@ -1168,6 +1173,7 @@ static int link_drop_dynamic_config(Link *link, Network *network) { RET_GATHER(r, link_drop_dhcp6_config(link, network)); RET_GATHER(r, link_drop_dhcp_pd_config(link, network)); link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */ link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 4c3e53b93e..5a3d1d9447 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -144,6 +144,7 @@ typedef struct Link { bool bearer_configured:1; sd_dhcp_relay_interface *dhcp_relay_interface; + sd_dhcp_relay_interface *dhcp_relay_interface_compat; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 4b54c6d88d..81b9784b49 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -369,9 +369,9 @@ DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, DHCPRelay.InterfacePriority, config_parse_int, 0, offsetof(Network, dhcp_relay_interface_priority) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 -DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) -DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id) -DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id) +DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_target_address) /* deprecated */ +DHCPServer.RelayAgentCircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) /* deprecated */ +DHCPServer.RelayAgentRemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_remote_id) /* deprecated */ DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.IPv6OnlyPreferredSec, config_parse_dhcp_server_ipv6_only_preferred, 0, offsetof(Network, dhcp_server_ipv6_only_preferred_usec) @@ -397,7 +397,7 @@ DHCPServer.PoolOffset, config_parse_uint32, DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) DHCPServer.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_extra_options) DHCPServer.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_vendor_options) -DHCPServer.BindToInterface, config_parse_bool, 0, offsetof(Network, dhcp_server_bind_to_interface) +DHCPServer.BindToInterface, config_parse_warn_compat, DISABLED_LEGACY, 0 DHCPServer.BootServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_boot_server_address) DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index b1569866d1..d63eeaa478 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -437,7 +437,6 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID, .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), - .dhcp_server_bind_to_interface = true, .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_SIP].emit = true, @@ -773,13 +772,12 @@ static Network *network_free(Network *network) { set_free(network->dnssec_negative_trust_anchors); /* DHCP relay agent */ + iovec_done(&network->dhcp_relay_remote_id); iovec_done(&network->dhcp_relay_circuit_id); iovec_done(&network->dhcp_relay_vss); tlv_done(&network->dhcp_relay_extra_options); /* DHCP server */ - free(network->dhcp_server_relay_agent_circuit_id); - free(network->dhcp_server_relay_agent_remote_id); free(network->dhcp_server_boot_server_name); free(network->dhcp_server_boot_filename); free(network->dhcp_server_timezone); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index d9e516ff4e..893b3022bb 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -212,8 +212,10 @@ typedef struct Network { /* DHCP Relay Agent Support */ DHCPRelayInterfaceMode dhcp_relay_interface_mode; Address *dhcp_relay_agent_address; + struct in_addr dhcp_relay_target_address; /* for deprecated DHCPServer.RelayTarget= */ struct in_addr dhcp_relay_agent_address_in_addr; struct in_addr dhcp_relay_gateway_address; + struct iovec dhcp_relay_remote_id; /* for deprecated DHCPServer.RelayAgentRemoteId= */ struct iovec dhcp_relay_circuit_id; struct iovec dhcp_relay_vss; TLV dhcp_relay_extra_options; @@ -221,15 +223,11 @@ typedef struct Network { /* DHCP Server Support */ bool dhcp_server; - bool dhcp_server_bind_to_interface; unsigned char dhcp_server_address_prefixlen; struct in_addr dhcp_server_address_in_addr; const Address *dhcp_server_address; int dhcp_server_uplink_index; char *dhcp_server_uplink_name; - struct in_addr dhcp_server_relay_target; - char *dhcp_server_relay_agent_circuit_id; - char *dhcp_server_relay_agent_remote_id; NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; bool dhcp_server_emit_router; struct in_addr dhcp_server_router; diff --git a/test/test-network/conf/25-agent-client-peer.network b/test/test-network/conf/25-agent-client-peer.network index 4d7d758d29..eaa3dfa572 100644 --- a/test/test-network/conf/25-agent-client-peer.network +++ b/test/test-network/conf/25-agent-client-peer.network @@ -3,13 +3,12 @@ Name=client-peer [Network] -Address=192.168.6.2/24 +Address=198.51.100.10/24 DHCPServer=yes -IPv4Forwarding=yes IPv6AcceptRA=no [DHCPServer] -RelayTarget=192.168.5.1 +RelayTarget=192.0.2.1 BindToInterface=no RelayAgentCircuitId=string:sample_circuit_id RelayAgentRemoteId=string:sample_remote_id diff --git a/test/test-network/conf/25-agent-client.network b/test/test-network/conf/25-agent-client.network index 219d40a9b7..731f8fe5b6 100644 --- a/test/test-network/conf/25-agent-client.network +++ b/test/test-network/conf/25-agent-client.network @@ -4,5 +4,4 @@ Name=client [Network] DHCP=yes -IPv4Forwarding=yes IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server-peer.network b/test/test-network/conf/25-agent-server-peer.network index 5e005c79ec..ef1e80fcad 100644 --- a/test/test-network/conf/25-agent-server-peer.network +++ b/test/test-network/conf/25-agent-server-peer.network @@ -3,6 +3,5 @@ Name=server-peer [Network] -Address=192.168.5.2/24 -IPv4Forwarding=yes +Address=192.0.2.2/24 IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server.network b/test/test-network/conf/25-agent-server.network deleted file mode 100644 index 0108039e6f..0000000000 --- a/test/test-network/conf/25-agent-server.network +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=server - -[Network] -Address=192.168.5.1/24 -IPv4Forwarding=yes -DHCPServer=yes -IPv6AcceptRA=no - -[DHCPServer] -BindToInterface=no -PoolOffset=150 -PoolSize=1 -DNS=192.168.5.1 -NTP=192.168.5.1 diff --git a/test/test-network/conf/25-agent-veth-server.netdev b/test/test-network/conf/25-agent-veth-server.netdev deleted file mode 100644 index 1427024d47..0000000000 --- a/test/test-network/conf/25-agent-veth-server.netdev +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[NetDev] -Name=server -Kind=veth -MACAddress=12:34:56:78:9b:bc - -[Peer] -Name=server-peer -MACAddress=12:34:56:78:9b:bd diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 28c418460c..3149628647 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -8364,11 +8364,22 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): tear_down_common() def test_relay_agent(self): + check_output('ip netns add ns-server') + check_output('ip link add server type veth peer server-peer') + check_output('ip link set server netns ns-server') + check_output('ip netns exec ns-server ip link set server up') + check_output('ip netns exec ns-server ip address add 192.0.2.1/24 dev server') + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + copy_network_unit( '25-agent-veth-client.netdev', - '25-agent-veth-server.netdev', '25-agent-client.network', - '25-agent-server.network', '25-agent-client-peer.network', '25-agent-server-peer.network', ) @@ -8378,7 +8389,7 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): output = networkctl_status('client') print(output) - self.assertRegex(output, r'Address: 192.168.5.150 \(DHCPv4 via 192.168.5.1\)') + self.assertRegex(output, r'Address: 198\.51\.100\.10[0-9] \(DHCPv4 via 192\.0\.2\.1\)') def test_relay_agent_on_bridge(self): copy_network_unit( @@ -8392,7 +8403,7 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): self.wait_online('bridge-relay:routable', 'client-peer:enslaved') # For issue #30763. - self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') + self.check_networkd_log('bridge-relay: Relaying DHCPv4 messages') def test_sd_dhcp_relay(self): check_output('ip netns add ns-relay') From 54c97ce6a478bdaa8a4fe88450696fd51fdeaa8c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 4 May 2026 08:43:41 +0900 Subject: [PATCH 8/9] sd-dhcp-server: drop legacy DHCP relay mode --- src/libsystemd-network/dhcp-option.c | 31 --- src/libsystemd-network/dhcp-server-internal.h | 8 - .../fuzz-dhcp-server-relay.c | 45 ---- src/libsystemd-network/meson.build | 3 - src/libsystemd-network/sd-dhcp-server.c | 220 ++---------------- src/libsystemd-network/test-dhcp-server.c | 13 +- src/systemd/sd-dhcp-server.h | 5 - .../sample1 | Bin .../sample2 | Bin .../too-large-packet | Bin 10 files changed, 20 insertions(+), 305 deletions(-) delete mode 100644 src/libsystemd-network/fuzz-dhcp-server-relay.c rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/sample1 (100%) rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/sample2 (100%) rename test/fuzz/{fuzz-dhcp-server-relay => fuzz-dhcp-relay}/too-large-packet (100%) diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index c5e8b29794..7ad203f183 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -7,7 +7,6 @@ #include "alloc-util.h" #include "dhcp-option.h" -#include "dhcp-server-internal.h" #include "dns-domain.h" #include "hostname-util.h" #include "memory-util.h" @@ -39,8 +38,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, assert(size > 0); assert(offset); - int r; - if (code != SD_DHCP_OPTION_END) /* always make sure there is space for an END option */ size--; @@ -68,34 +65,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 3 + optlen; break; - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: { - /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. - * The structured handling below expects optval to be an sd_dhcp_server*. */ - if (optlen > 0) - return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); - - sd_dhcp_server *server = (sd_dhcp_server *) optval; - size_t current_offset = *offset + 2; - - if (server->agent_circuit_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, - strlen(server->agent_circuit_id), server->agent_circuit_id); - if (r < 0) - return r; - } - if (server->agent_remote_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID, - strlen(server->agent_remote_id), server->agent_remote_id); - if (r < 0) - return r; - } - - options[*offset] = code; - options[*offset + 1] = current_offset - *offset - 2; - assert(current_offset - *offset - 2 <= UINT8_MAX); - *offset = current_offset; - break; - } default: return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); } diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index a8e2cbf5c7..d4caae66d4 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -32,14 +32,11 @@ typedef struct sd_dhcp_server { sd_event *event; int event_priority; sd_event_source *receive_message; - sd_event_source *receive_broadcast; int fd; int fd_raw; - int fd_broadcast; int ifindex; char *ifname; - bool bind_to_interface; be32_t address; be32_t netmask; be32_t subnet; @@ -73,11 +70,6 @@ typedef struct sd_dhcp_server { sd_dhcp_server_callback_t callback; void *callback_userdata; - struct in_addr relay_target; - - char *agent_circuit_id; - char *agent_remote_id; - int lease_dir_fd; char *lease_file; } sd_dhcp_server; diff --git a/src/libsystemd-network/fuzz-dhcp-server-relay.c b/src/libsystemd-network/fuzz-dhcp-server-relay.c deleted file mode 100644 index b2c881c067..0000000000 --- a/src/libsystemd-network/fuzz-dhcp-server-relay.c +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "fuzz.h" -#include "sd-dhcp-server.c" - -ssize_t sendto(int __fd, const void *__buf, size_t __n, int flags, const struct sockaddr *__addr, socklen_t __addr_len) { - return __n; -} - -ssize_t sendmsg(int __fd, const struct msghdr *__message, int flags) { - return 0; -} - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - struct in_addr address = {.s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - union in_addr_union relay_address; - _cleanup_free_ uint8_t *message = NULL; - - if (size < sizeof(DHCPMessage)) - return 0; - - fuzz_setup_logging(); - - assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); - assert_se(in_addr_from_string(AF_INET, "192.168.5.1", &relay_address) >= 0); - assert_se(sd_dhcp_server_set_relay_target(server, &relay_address.in) >= 0); - assert_se(sd_dhcp_server_set_bind_to_interface(server, false) >= 0); - assert_se(sd_dhcp_server_set_relay_agent_information(server, "string:sample_circuit_id", "string:sample_remote_id") >= 0); - - size_t buflen = size; - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - assert_se(message = malloc(buflen)); - memcpy(message, data, size); - - server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(server->fd >= 0); - - (void) dhcp_server_relay_message(server, (DHCPMessage *) message, size - sizeof(DHCPMessage), buflen); - return 0; -} diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 5d2368cc98..9b36f6edf2 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -145,9 +145,6 @@ executables += [ network_fuzz_template + { 'sources' : files('fuzz-dhcp-server.c'), }, - network_fuzz_template + { - 'sources' : files('fuzz-dhcp-server-relay.c'), - }, network_fuzz_template + { 'sources' : files('fuzz-lldp-rx.c'), }, diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fc28469a86..07f15ef6dc 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -113,12 +113,6 @@ int sd_dhcp_server_is_running(sd_dhcp_server *server) { return !!server->receive_message; } -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) { - assert_return(server, -EINVAL); - - return in4_addr_is_set(&server->relay_target); -} - static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { assert(server); @@ -142,9 +136,6 @@ static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { tlv_unref(server->extra_options); tlv_unref(server->vendor_options); - free(server->agent_circuit_id); - free(server->agent_remote_id); - safe_close(server->lease_dir_fd); free(server->lease_file); @@ -168,11 +159,9 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { .n_ref = 1, .fd_raw = -EBADF, .fd = -EBADF, - .fd_broadcast = -EBADF, .address = htobe32(INADDR_ANY), .netmask = htobe32(INADDR_ANY), .ifindex = ifindex, - .bind_to_interface = true, .default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC, .max_lease_time = DHCP_MAX_LEASE_TIME_USEC, .rapid_commit = true, @@ -289,11 +278,9 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { running = sd_dhcp_server_is_running(server); server->receive_message = sd_event_source_disable_unref(server->receive_message); - server->receive_broadcast = sd_event_source_disable_unref(server->receive_broadcast); server->fd_raw = safe_close(server->fd_raw); server->fd = safe_close(server->fd); - server->fd_broadcast = safe_close(server->fd_broadcast); if (running) log_dhcp_server(server, "STOPPED"); @@ -385,27 +372,21 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, assert(message); assert(len >= sizeof(DHCPMessage)); - if (server->bind_to_interface) { - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); - cmsg = CMSG_FIRSTHDR(&msg); - assert(cmsg); + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - /* we attach source interface and address info to the message - rather than binding the socket. This will be mostly useful - when we gain support for arbitrary number of server addresses - */ - pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); - assert(pktinfo); + pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); + assert(pktinfo); - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - } + pktinfo->ipi_ifindex = server->ifindex; + pktinfo->ipi_spec_dst.s_addr = server->address; if (sendmsg(server->fd, &msg, 0) < 0) return -errno; @@ -1000,83 +981,6 @@ static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { return true; } -static int append_agent_information_option(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t size) { - int r; - size_t offset; - - assert(server); - assert(message); - - r = dhcp_option_find_option(message->options, opt_length, SD_DHCP_OPTION_END, &offset); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, 0, server); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - return offset; -} - -static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t buflen) { - _cleanup_free_ DHCPPacket *packet = NULL; - int r; - - assert(server); - assert(message); - assert(sd_dhcp_server_is_in_relay_mode(server)); - - if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen)) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) received message without/invalid hardware address, discarding."); - - if (message->op == BOOTREQUEST) { - log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid)); - if (message->hops >= 16) - return -ETIME; - message->hops++; - - /* https://tools.ietf.org/html/rfc1542#section-4.1.1 */ - if (message->giaddr == 0) - message->giaddr = server->address; - - if (server->agent_circuit_id || server->agent_remote_id) { - r = append_agent_information_option(server, message, opt_length, buflen - sizeof(DHCPMessage)); - if (r < 0) - return log_dhcp_server_errno(server, r, "could not append relay option: %m"); - opt_length = r; - } - - return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length); - } else if (message->op == BOOTREPLY) { - log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid)); - if (message->giaddr != server->address) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) BOOTREPLY giaddr mismatch, discarding"); - - int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL); - if (message_type < 0) - return message_type; - - packet = malloc0(sizeof(DHCPPacket) + opt_length); - if (!packet) - return -ENOMEM; - memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length); - - r = dhcp_option_remove_option(packet->dhcp.options, opt_length, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); - if (r > 0) - opt_length = r; - - bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK; - const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr; - return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast); - } - return -EBADMSG; -} - static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { usec_t expiration; int r; @@ -1315,15 +1219,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz return 0; } -static size_t relay_agent_information_length(const char* agent_circuit_id, const char* agent_remote_id) { - size_t sum = 0; - if (agent_circuit_id) - sum += 2 + strlen(agent_circuit_id); - if (agent_remote_id) - sum += 2 + strlen(agent_remote_id); - return sum; -} - static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_free_ DHCPMessage *message = NULL; @@ -1351,10 +1246,6 @@ static int server_receive_message(sd_event_source *s, int fd, } size_t buflen = datagram_size; - if (sd_dhcp_server_is_in_relay_mode(server)) - /* Preallocate the additional size for DHCP Relay Agent Information Option if needed */ - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - message = malloc0(buflen); if (!message) return -ENOMEM; @@ -1377,15 +1268,10 @@ static int server_receive_message(sd_event_source *s, int fd, if (info && info->ipi_ifindex != server->ifindex) return 0; - if (sd_dhcp_server_is_in_relay_mode(server)) { - r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't relay message, ignoring: %m"); - } else { - r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); - } + r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + if (r < 0) + log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); + return 0; } @@ -1424,10 +1310,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { } server->fd_raw = r; - if (server->bind_to_interface) - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); - else - r = dhcp_network_bind_udp_socket(0, server->address, DHCP_PORT_SERVER, -1); + r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); if (r < 0) goto on_error; server->fd = r; @@ -1443,25 +1326,6 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { if (r < 0) goto on_error; - if (!server->bind_to_interface) { - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_BROADCAST, DHCP_PORT_SERVER, -1); - if (r < 0) - goto on_error; - - server->fd_broadcast = r; - - r = sd_event_add_io(server->event, &server->receive_broadcast, - server->fd_broadcast, EPOLLIN, - server_receive_message, server); - if (r < 0) - goto on_error; - - r = sd_event_source_set_priority(server->receive_broadcast, - server->event_priority); - if (r < 0) - goto on_error; - } - r = dhcp_server_load_leases(server); if (r < 0) log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); @@ -1490,18 +1354,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { return r; } -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled) { - assert_return(server, -EINVAL); - assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - - if (!!enabled == server->bind_to_interface) - return 0; - - server->bind_to_interface = enabled; - - return 1; -} - int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { int r; @@ -1649,46 +1501,6 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_ return 0; } -int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr *address) { - assert_return(server, -EINVAL); - assert_return(address, -EINVAL); - assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - - if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0) - return 0; - - server->relay_target = *address; - return 1; -} - -int sd_dhcp_server_set_relay_agent_information( - sd_dhcp_server *server, - const char *agent_circuit_id, - const char *agent_remote_id) { - _cleanup_free_ char *circuit_id_dup = NULL, *remote_id_dup = NULL; - - assert_return(server, -EINVAL); - - if (relay_agent_information_length(agent_circuit_id, agent_remote_id) > UINT8_MAX) - return -ENOBUFS; - - if (agent_circuit_id) { - circuit_id_dup = strdup(agent_circuit_id); - if (!circuit_id_dup) - return -ENOMEM; - } - - if (agent_remote_id) { - remote_id_dup = strdup(agent_remote_id); - if (!remote_id_dup) - return -ENOMEM; - } - - free_and_replace(server->agent_circuit_id, circuit_id_dup); - free_and_replace(server->agent_remote_id, remote_id_dup); - return 0; -} - int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path) { int r; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 4da4fdb116..34263f044d 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -22,7 +22,7 @@ static void test_pool(struct in_addr *address, unsigned size, int ret) { ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret)); } -static int test_basic(bool bind_to_interface) { +static int test_basic(void) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; struct in_addr address_lo = { @@ -33,14 +33,13 @@ static int test_basic(bool bind_to_interface) { }; int r; - log_debug("/* %s(bind_to_interface=%s) */", __func__, yes_no(bind_to_interface)); + log_debug("/* %s */", __func__); ASSERT_OK(sd_event_new(&event)); /* attach to loopback interface */ ASSERT_OK(sd_dhcp_server_new(&server, 1)); ASSERT_NOT_NULL(server); - server->bind_to_interface = bind_to_interface; ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0)); ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY)); @@ -410,13 +409,9 @@ int main(int argc, char *argv[]) { test_static_lease(); test_domain_name(); - r = test_basic(true); + r = test_basic(); if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(bound to interface)"); - - r = test_basic(false); - if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(non-bound to interface)"); + return log_tests_skipped_errno(r, "cannot start dhcp server"); test_message_handler(); diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 1fb7d2e9ee..8c3dbab9b4 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -57,7 +57,6 @@ int sd_dhcp_server_configure_pool(sd_dhcp_server *server, const struct in_addr * int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address); int sd_dhcp_server_set_boot_server_name(sd_dhcp_server *server, const char *name); int sd_dhcp_server_set_boot_filename(sd_dhcp_server *server, const char *filename); -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled); int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz); int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name); int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *router); @@ -90,10 +89,6 @@ int sd_dhcp_server_set_rapid_commit(sd_dhcp_server *server, int enabled); int sd_dhcp_server_forcerenew(sd_dhcp_server *server); -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server); -int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address); -int sd_dhcp_server_set_relay_agent_information(sd_dhcp_server *server, const char* circuit_id, const char* remote_id); - int sd_dhcp_server_get_lease_address_by_name(sd_dhcp_server *server, const char *name, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample1 b/test/fuzz/fuzz-dhcp-relay/sample1 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample1 rename to test/fuzz/fuzz-dhcp-relay/sample1 diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample2 b/test/fuzz/fuzz-dhcp-relay/sample2 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample2 rename to test/fuzz/fuzz-dhcp-relay/sample2 diff --git a/test/fuzz/fuzz-dhcp-server-relay/too-large-packet b/test/fuzz/fuzz-dhcp-relay/too-large-packet similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/too-large-packet rename to test/fuzz/fuzz-dhcp-relay/too-large-packet From 51120d1d31a311199e18250e8c7bcee9ecbe3115 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 12 May 2026 15:15:08 +0900 Subject: [PATCH 9/9] NEWS: mention new DHCP relay agent --- NEWS | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/NEWS b/NEWS index 386b803cf6..42705e21f6 100644 --- a/NEWS +++ b/NEWS @@ -73,6 +73,27 @@ CHANGES WITH 261 in spe: effectively means live upgrades from versions older than v247 are not supported anymore. + * systemd-networkd gained a new sd-dhcp-relay backend for DHCP relay + agent support. As part of this change, the following [DHCPServer] + settings are deprecated: + - BindToInterface= + - RelayTarget= + - RelayAgentCircuitId= + - RelayAgentRemoteId= + They are replaced by DHCPRelay= in [Network], along with new + [DHCPRelay] section settings in .network files: + - AgentAddress= + - GatewayAddress= + - CircuitId= + - VirtualSubnetSelection= + - ExtraOption= + - InterfacePriority= + and in networkd.conf: + - ServerAddress= + - OverrideServerIdentifier= + - RemoteId= + - ExtraOption= + * Required version of musl (when built with -Dlibc=musl) has been raised from 1.2.5 to 1.2.6.