sd-dhcp-relay: reimplement DHCP relay agent and drop legacy DHCP relay mode from sd-dhcp-server (#42130)
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "dhcp-option.h"
|
||||
#include "dhcp-server-internal.h"
|
||||
#include "dns-domain.h"
|
||||
#include "hostname-util.h"
|
||||
#include "memory-util.h"
|
||||
@@ -39,8 +38,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset,
|
||||
assert(size > 0);
|
||||
assert(offset);
|
||||
|
||||
int r;
|
||||
|
||||
if (code != SD_DHCP_OPTION_END)
|
||||
/* always make sure there is space for an END option */
|
||||
size--;
|
||||
@@ -68,34 +65,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset,
|
||||
*offset += 3 + optlen;
|
||||
|
||||
break;
|
||||
case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: {
|
||||
/* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV.
|
||||
* The structured handling below expects optval to be an sd_dhcp_server*. */
|
||||
if (optlen > 0)
|
||||
return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
|
||||
|
||||
sd_dhcp_server *server = (sd_dhcp_server *) optval;
|
||||
size_t current_offset = *offset + 2;
|
||||
|
||||
if (server->agent_circuit_id) {
|
||||
r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID,
|
||||
strlen(server->agent_circuit_id), server->agent_circuit_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (server->agent_remote_id) {
|
||||
r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID,
|
||||
strlen(server->agent_remote_id), server->agent_remote_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
options[*offset] = code;
|
||||
options[*offset + 1] = current_offset - *offset - 2;
|
||||
assert(current_offset - *offset - 2 <= UINT8_MAX);
|
||||
*offset = current_offset;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'),
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user