sd-dhcp-relay: reimplement DHCP relay agent and drop legacy DHCP relay mode from sd-dhcp-server (#42130)

This commit is contained in:
Daan De Meyer
2026-05-21 16:23:44 +02:00
committed by GitHub
53 changed files with 2674 additions and 470 deletions
+21
View File
@@ -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.
+61
View File
@@ -393,6 +393,67 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00</programlisting>
</variablelist>
</refsect1>
<refsect1>
<title>[DHCPRelay] Section Options</title>
<para>The [DHCPRelay] section contains host-wide settings for the DHCP relay agent.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>ServerAddress=</varname></term>
<listitem>
<para>Specifies the IPv4 address of the upstream DHCP server. This is required for the host to act
as a DHCP relay agent.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>OverrideServerIdentifier=</varname></term>
<listitem>
<para>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.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RemoteId=</varname></term>
<listitem>
<para>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
(<literal><replaceable>type</replaceable>:<replaceable>value</replaceable></literal>). The type
takes one of <literal>uint8</literal>, <literal>uint16</literal>, <literal>uint32</literal>,
<literal>ipv4address</literal>, <literal>ipv6address</literal>, or <literal>string</literal>.
Special characters in the data string may be escaped using
<ulink url="https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences">C-style escapes</ulink>.
If unset, a UUID generated from the local machine ID will be used.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ExtraOption=</varname></term>
<listitem>
<para>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
(<literal><replaceable>code</replaceable>:<replaceable>type</replaceable>:<replaceable>value</replaceable></literal>).
The code is an integer in 1…254. See <varname>RemoteId=</varname> 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.
</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[DHCPServer] Section Options</title>
+121 -53
View File
@@ -435,6 +435,26 @@
</listitem>
</varlistentry>
<varlistentry>
<term><varname>DHCPRelay=</varname></term>
<listitem>
<para>Takes one of <literal>upstream</literal> or <literal>downstream</literal>. When specified,
the host acts as a DHCP relay agent. When set to <literal>upstream</literal>, the interface behaves
as an upstream interface of the DHCP relay agent. When set to <literal>downstream</literal>, 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 <varname>ServerAddress=</varname> in the [DHCPRelay] section to be configured in
<filename>networkd.conf</filename>. If <varname>ServerAddress=</varname> is not configured, this
setting is ignored. See
<citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details about <varname>ServerAddress=</varname> 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.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>DHCPServer=</varname></term>
<listitem>
@@ -444,10 +464,9 @@
<para>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
<varname>RelayTarget=</varname> or <varname>PersistLeases=no/runtime</varname> are specified in the
[DHCPServer] section. It will be started after
<filename>systemd-networkd-persistent-storage.service</filename> is started, which calls
<command>networkctl persistent-storage yes</command>. See
<varname>PersistLeases=no/runtime</varname> is specified in the [DHCPServer] section. It will be
started after <filename>systemd-networkd-persistent-storage.service</filename> is started, which
calls <command>networkctl persistent-storage yes</command>. See
<citerefentry><refentrytitle>networkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for more details.</para>
@@ -3868,6 +3887,104 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
</variablelist>
</refsect1>
<refsect1>
<title>[DHCPRelay] Section Options</title>
<para>The [DHCPRelay] section contains per-interface settings for the DHCP relay agent. The settings in
this section are only used when <varname>DHCPRelay=</varname> in the [Network] section is configured.
</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>AgentAddress=</varname></term>
<listitem>
<para>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.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>GatewayAddress=</varname></term>
<listitem>
<para>Takes an IPv4 address. The specified address is set to the <literal>giaddr</literal> field of
the DHCP message header when the DHCP message is forwarded to the upstream DHCP server. This is
used only when <varname>DHCPRelay=downstream</varname>, and ignored otherwise. Defaults to unset,
and the same address specified with <varname>AgentAddress=</varname> will be used.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>CircuitId=</varname></term>
<listitem>
<para>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
(<literal><replaceable>type</replaceable>:<replaceable>value</replaceable></literal>). The type
takes one of <literal>uint8</literal>, <literal>uint16</literal>, <literal>uint32</literal>,
<literal>ipv4address</literal>, <literal>ipv6address</literal>, or <literal>string</literal>.
Special characters in the data string may be escaped using
<ulink url="https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences">C-style escapes</ulink>.
This is used only when <varname>DHCPRelay=downstream</varname>, and ignored otherwise. If unset,
the name of the interface will be used.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>VirtualSubnetSelection=</varname></term>
<listitem>
<para>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
<varname>CircuitId=</varname>. This is used only when <varname>DHCPRelay=downstream</varname>, and
ignored otherwise. Defaults to unset, and the sub-option is not set.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ExtraOption=</varname></term>
<listitem>
<para>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
(<literal><replaceable>code</replaceable>:<replaceable>type</replaceable>:<replaceable>value</replaceable></literal>).
The code is an integer in 1…254. See <varname>CircuitId=</varname> 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
<varname>DHCPRelay=downstream</varname>, and ignored otherwise. Defaults to unset, and no extra
sub-option will be appended.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>InterfacePriority=</varname></term>
<listitem>
<para>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
<varname>DHCPRelay=upstream</varname>, and ignored otherwise. Defaults to zero.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[DHCPServer] Section Options</title>
<para>The [DHCPServer] section contains settings for the DHCP server, if enabled via the
@@ -4161,52 +4278,6 @@ ServerAddress=192.168.0.1/24</programlisting>
<xi:include href="version-info.xml" xpointer="v246"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>BindToInterface=</varname></term>
<listitem>
<para>Takes a boolean value. When <literal>yes</literal>, DHCP server socket will be bound
to its network interface and all socket communication will be restricted to this interface.
Defaults to <literal>yes</literal>, except if <varname>RelayTarget=</varname> is used (see below),
in which case it defaults to <literal>no</literal>.</para>
<xi:include href="version-info.xml" xpointer="v249"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RelayTarget=</varname></term>
<listitem>
<para>Takes an IPv4 address, which must be in the format described in
<citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
Turns this DHCP server into a DHCP relay agent. See <ulink url="https://tools.ietf.org/html/rfc1542">RFC 1542</ulink>.
The address is the address of DHCP server or another relay agent to forward DHCP messages to and from.</para>
<xi:include href="version-info.xml" xpointer="v249"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RelayAgentCircuitId=</varname></term>
<listitem>
<para>Specifies value for Agent Circuit ID suboption of Relay Agent Information option.
Takes a string, which must be in the format <literal>string:<replaceable>value</replaceable></literal>,
where <literal><replaceable>value</replaceable></literal> should be replaced with the value of the suboption.
Defaults to unset (means no Agent Circuit ID suboption is generated).
Ignored if <varname>RelayTarget=</varname> is not specified.</para>
<xi:include href="version-info.xml" xpointer="v249"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RelayAgentRemoteId=</varname></term>
<listitem>
<para>Specifies value for Agent Remote ID suboption of Relay Agent Information option.
Takes a string, which must be in the format <literal>string:<replaceable>value</replaceable></literal>,
where <literal><replaceable>value</replaceable></literal> should be replaced with the value of the suboption.
Defaults to unset (means no Agent Remote ID suboption is generated).
Ignored if <varname>RelayTarget=</varname> is not specified.</para>
<xi:include href="version-info.xml" xpointer="v249"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RapidCommit=</varname></term>
@@ -4238,9 +4309,6 @@ ServerAddress=192.168.0.1/24</programlisting>
<citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
which defaults to <literal>yes</literal>, will be used.</para>
<para>When <varname>RelayTarget=</varname> is specified, this setting will be ignored and no leases
will be saved, as there will be no bound lease on the server.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
-31
View File
@@ -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, &current_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, &current_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);
}
@@ -0,0 +1,503 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#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);
}
@@ -0,0 +1,334 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/ip.h>
#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;
}
@@ -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__)
@@ -0,0 +1,150 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#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;
}
@@ -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;
+61
View File
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/socket.h>
#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;
}
@@ -1,45 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#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;
}
+10 -3
View File
@@ -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',
@@ -92,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'),
},
@@ -129,15 +136,15 @@ 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'),
},
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'),
},
+136
View File
@@ -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);
}
+16 -204
View File
@@ -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;
+383
View File
@@ -0,0 +1,383 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#include <sys/socket.h>
#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);
+4 -9
View File
@@ -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();
+3 -1
View File
@@ -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;
+1
View File
@@ -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',
+7
View File
@@ -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;
}
@@ -925,6 +929,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) {
@@ -2517,5 +2523,6 @@ int network_drop_invalid_addresses(Network *network) {
if (r < 0)
return r;
network_adjust_dhcp_relay(network);
return 0;
}
+1
View File
@@ -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,
+380
View File
@@ -0,0 +1,380 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/rtnetlink.h>
#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);
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;
}
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;
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;
}
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);
+20
View File
@@ -0,0 +1,20 @@
/* 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_COMPAT,
_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);
+4 -13
View File
@@ -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);
}
+26 -49
View File
@@ -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,
-1
View File
@@ -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);
+4
View File
@@ -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
+1 -2
View File
@@ -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)
+28
View File
@@ -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,8 @@ 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_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);
@@ -424,6 +428,14 @@ 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_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"));
@@ -739,6 +751,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");
@@ -1196,6 +1212,8 @@ 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_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);
@@ -1314,6 +1332,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;
@@ -2645,6 +2667,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)
+2
View File
@@ -143,6 +143,8 @@ typedef struct Link {
bool bridge_vlan_set:1;
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;
+7
View File
@@ -5,6 +5,7 @@
#include <sys/socket.h>
#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);
+8
View File
@@ -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;
+11 -4
View File
@@ -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,11 +361,17 @@ 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)
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)
@@ -390,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)
+11 -3
View File
@@ -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,7 +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_server_bind_to_interface = true,
.dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID,
.dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION),
.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,
@@ -547,6 +550,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,9 +771,13 @@ 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_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);
+13 -4
View File
@@ -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,17 +209,25 @@ 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_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;
int dhcp_relay_interface_priority;
/* 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;
+1
View File
@@ -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",
+1
View File
@@ -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,
+1
View File
@@ -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',
+71
View File
@@ -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 <https://www.gnu.org/licenses/>.
***/
#include <netinet/in.h>
#include <sys/uio.h>
#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
-5
View File
@@ -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);
Binary file not shown.
Binary file not shown.
@@ -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
@@ -4,5 +4,4 @@ Name=client
[Network]
DHCP=yes
IPv4Forwarding=yes
IPv6AcceptRA=no
@@ -3,6 +3,5 @@
Name=server-peer
[Network]
Address=192.168.5.2/24
IPv4Forwarding=yes
Address=192.0.2.2/24
IPv6AcceptRA=no
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+125 -4
View File
@@ -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
@@ -8384,11 +8385,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',
)
@@ -8398,7 +8410,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(
@@ -8412,7 +8424,116 @@ 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')
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):