Merge pull request #29119 from yuwata/network-dhcp-server-allow-null-server-address

network/dhcp-server: allow null server address
This commit is contained in:
Luca Boccassi
2023-09-17 12:36:33 +01:00
committed by GitHub
9 changed files with 157 additions and 90 deletions

View File

@@ -3277,12 +3277,43 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
<varlistentry>
<term><varname>ServerAddress=</varname></term>
<listitem><para>Specifies server address for the DHCP server. Takes an IPv4 address with prefix
length, for example 192.168.0.1/24. This setting may be useful when the link on
which the DHCP server is running has multiple static addresses. When unset, one of static addresses
in the link will be automatically selected. Defaults to unset.</para>
<listitem>
<para>Specifies the server address for the DHCP server. Takes an IPv4 address with prefix length
separated with a slash, e.g. <literal>192.168.0.1/24</literal>. Defaults to unset, and one of
static IPv4 addresses configured in [Network] or [Address] section will be automatically selected.
This setting may be useful when the interface on which the DHCP server is running has multiple
static IPv4 addresses.</para>
<para>This implies <varname>Address=</varname> in [Network] or [Address] section with the same
address and prefix length. That is,
<programlisting>[Network]
DHCPServer=yes
Address=192.168.0.1/24
Address=192.168.0.2/24
[DHCPServer]
ServerAddress=192.168.0.1/24</programlisting>
or
<programlisting>[Network]
DHCPServer=yes
[Address]
Address=192.168.0.1/24
[Address]
Address=192.168.0.2/24
[DHCPServer]
ServerAddress=192.168.0.1/24</programlisting>
are equivalent to the following.
<programlisting>[Network]
DHCPServer=yes
Address=192.168.0.2/24
[DHCPServer]
ServerAddress=192.168.0.1/24</programlisting>
</para>
<para>Since version 255, like the <varname>Address=</varname> setting in [Network] or [Address]
section, this also supports a null address, e.g. <literal>0.0.0.0/24</literal>, and an unused
address will be automatically selected. For more details about the automatic address selection,
see <varname>Address=</varname> setting in [Network] section in the above.</para>
<xi:include href="version-info.xml" xpointer="v249"/></listitem>
<xi:include href="version-info.xml" xpointer="v249"/>
</listitem>
</varlistentry>
<varlistentry>

View File

@@ -110,7 +110,7 @@ int address_new(Address **ret) {
return 0;
}
static int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) {
int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) {
_cleanup_(config_section_freep) ConfigSection *n = NULL;
_cleanup_(address_freep) Address *address = NULL;
int r;
@@ -401,7 +401,7 @@ static int address_compare_func(const Address *a1, const Address *a2) {
}
}
DEFINE_PRIVATE_HASH_OPS(
DEFINE_HASH_OPS(
address_hash_ops,
Address,
address_hash_func,
@@ -1561,10 +1561,6 @@ int link_request_static_addresses(Link *link) {
if (r < 0)
return r;
r = link_request_dhcp_server_address(link);
if (r < 0)
return r;
if (link->static_address_messages == 0) {
link->static_addresses_configured = true;
link_check_ready(link);
@@ -2334,7 +2330,7 @@ static void address_section_adjust_broadcast(Address *address) {
address->broadcast.s_addr = 0;
}
static int address_section_verify(Address *address) {
int address_section_verify(Address *address) {
if (section_is_invalid(address->section))
return -EINVAL;
@@ -2444,6 +2440,10 @@ int network_drop_invalid_addresses(Network *network) {
assert(r > 0);
}
r = network_adjust_dhcp_server(network, &addresses);
if (r < 0)
return r;
return 0;
}

View File

@@ -7,6 +7,7 @@
#include "conf-parser.h"
#include "firewall-util.h"
#include "hash-funcs.h"
#include "in-addr-util.h"
#include "networkd-link.h"
#include "networkd-util.h"
@@ -73,7 +74,10 @@ const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) _warn_unu
int address_flags_to_string_alloc(uint32_t flags, int family, char **ret);
extern const struct hash_ops address_hash_ops;
int address_new(Address **ret);
int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret);
Address* address_free(Address *address);
int address_get(Link *link, const Address *in, Address **ret);
int address_get_harder(Link *link, const Address *in, Address **ret);
@@ -115,6 +119,7 @@ int link_request_static_addresses(Link *link);
int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, Manager *m);
int address_section_verify(Address *address);
int network_drop_invalid_addresses(Network *network);
DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Address, address);

View File

@@ -38,26 +38,31 @@ static bool link_dhcp4_server_enabled(Link *link) {
return link->network->dhcp_server;
}
void network_adjust_dhcp_server(Network *network) {
int network_adjust_dhcp_server(Network *network, Set **addresses) {
int r;
assert(network);
assert(addresses);
if (!network->dhcp_server)
return;
return 0;
if (network->bond) {
log_warning("%s: DHCPServer= is enabled for bond slave. Disabling DHCP server.",
network->filename);
network->dhcp_server = false;
return;
return 0;
}
if (!in4_addr_is_set(&network->dhcp_server_address)) {
assert(network->dhcp_server_address_prefixlen <= 32);
if (network->dhcp_server_address_prefixlen == 0) {
Address *address;
bool have = false;
/* If the server address is not specified, then find suitable static address. */
ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) {
if (section_is_invalid(address->section))
continue;
assert(!section_is_invalid(address->section));
if (address->family != AF_INET)
continue;
@@ -71,83 +76,69 @@ void network_adjust_dhcp_server(Network *network) {
if (in4_addr_is_set(&address->in_addr_peer.in))
continue;
have = true;
/* TODO: check if the prefix length is small enough for the pool. */
network->dhcp_server_address = address;
break;
}
if (!have) {
log_warning("%s: DHCPServer= is enabled, but no static address configured. "
if (!network->dhcp_server_address) {
log_warning("%s: DHCPServer= is enabled, but no suitable static address configured. "
"Disabling DHCP server.",
network->filename);
network->dhcp_server = false;
return;
return 0;
}
}
}
int link_request_dhcp_server_address(Link *link) {
_cleanup_(address_freep) Address *address = NULL;
Address *existing;
int r;
} else {
_cleanup_(address_freep) Address *a = NULL;
Address *existing;
unsigned line;
assert(link);
assert(link->network);
/* TODO: check if the prefix length is small enough for the pool. */
if (!link_dhcp4_server_enabled(link))
return 0;
/* If an address is explicitly specified, then check if the corresponding [Address] section
* is configured, and add one if not. */
if (!in4_addr_is_set(&link->network->dhcp_server_address))
return 0;
existing = set_get(*addresses,
&(Address) {
.family = AF_INET,
.in_addr.in = network->dhcp_server_address_in_addr,
.prefixlen = network->dhcp_server_address_prefixlen,
});
if (existing) {
/* Corresponding [Address] section already exists. */
network->dhcp_server_address = existing;
return 0;
}
r = address_new(&address);
if (r < 0)
return r;
r = ordered_hashmap_by_section_find_unused_line(network->addresses_by_section, network->filename, &line);
if (r < 0)
return log_warning_errno(r, "%s: Failed to find unused line number for DHCP server address: %m",
network->filename);
address->source = NETWORK_CONFIG_SOURCE_STATIC;
address->family = AF_INET;
address->in_addr.in = link->network->dhcp_server_address;
address->prefixlen = link->network->dhcp_server_address_prefixlen;
r = address_new_static(network, network->filename, line, &a);
if (r < 0)
return log_warning_errno(r, "%s: Failed to add new static address object for DHCP server: %m",
network->filename);
if (address_get_harder(link, address, &existing) >= 0 &&
(address_exists(existing) || address_is_requesting(existing)) &&
existing->source == NETWORK_CONFIG_SOURCE_STATIC)
/* The same address seems explicitly configured in [Address] or [Network] section.
* Configure the DHCP server address only when it is not. */
return 0;
a->family = AF_INET;
a->prefixlen = network->dhcp_server_address_prefixlen;
a->in_addr.in = network->dhcp_server_address_in_addr;
a->requested_as_null = !in4_addr_is_set(&network->dhcp_server_address_in_addr);
return link_request_static_address(link, address);
}
r = address_section_verify(a);
if (r < 0)
return r;
static int link_find_dhcp_server_address(Link *link, Address **ret) {
Address *address;
r = set_ensure_put(addresses, &address_hash_ops, a);
if (r < 0)
return log_oom();
assert(r > 0);
assert(link);
assert(link->network);
/* If ServerAddress= is specified, then use the address. */
if (in4_addr_is_set(&link->network->dhcp_server_address))
return link_get_ipv4_address(link, &link->network->dhcp_server_address,
link->network->dhcp_server_address_prefixlen, ret);
/* If not, then select one from static addresses. */
SET_FOREACH(address, link->addresses) {
if (address->source != NETWORK_CONFIG_SOURCE_STATIC)
continue;
if (!address_exists(address))
continue;
if (address->family != AF_INET)
continue;
if (in4_addr_is_localhost(&address->in_addr.in))
continue;
if (in4_addr_is_link_local(&address->in_addr.in))
continue;
if (in4_addr_is_set(&address->in_addr_peer.in))
continue;
*ret = address;
return 0;
network->dhcp_server_address = TAKE_PTR(a);
}
return -ENOENT;
return 0;
}
static int dhcp_server_find_uplink(Link *link, Link **ret) {
@@ -369,6 +360,8 @@ static int dhcp4_server_configure(Link *link) {
int r;
assert(link);
assert(link->network);
assert(link->network->dhcp_server_address);
log_link_debug(link, "Configuring DHCP Server.");
@@ -387,7 +380,7 @@ static int dhcp4_server_configure(Link *link) {
if (r < 0)
return log_link_warning_errno(link, r, "Failed to set callback for DHCPv4 server instance: %m");
r = link_find_dhcp_server_address(link, &address);
r = address_get(link, link->network->dhcp_server_address, &address);
if (r < 0)
return log_link_error_errno(link, r, "Failed to find suitable address for DHCPv4 server instance: %m");
@@ -535,6 +528,8 @@ static bool dhcp_server_is_ready_to_configure(Link *link) {
Address *a;
assert(link);
assert(link->network);
assert(link->network->dhcp_server_address);
if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false))
return false;
@@ -545,7 +540,7 @@ static bool dhcp_server_is_ready_to_configure(Link *link) {
if (!link->static_addresses_configured)
return false;
if (link_find_dhcp_server_address(link, &a) < 0)
if (address_get(link, link->network->dhcp_server_address, &a) < 0)
return false;
if (!address_is_ready(a))
@@ -711,7 +706,7 @@ int config_parse_dhcp_server_address(
assert(rvalue);
if (isempty(rvalue)) {
network->dhcp_server_address = (struct in_addr) {};
network->dhcp_server_address_in_addr = (struct in_addr) {};
network->dhcp_server_address_prefixlen = 0;
return 0;
}
@@ -722,14 +717,14 @@ int config_parse_dhcp_server_address(
"Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
return 0;
}
if (in4_addr_is_null(&a.in) || in4_addr_is_localhost(&a.in)) {
if (in4_addr_is_localhost(&a.in) || in4_addr_is_link_local(&a.in)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"DHCP server address cannot be the ANY address or a localhost address, "
"DHCP server address cannot be a localhost or link-local address, "
"ignoring assignment: %s", rvalue);
return 0;
}
network->dhcp_server_address = a.in;
network->dhcp_server_address_in_addr = a.in;
network->dhcp_server_address_prefixlen = prefixlen;
return 0;
}

View File

@@ -2,13 +2,13 @@
#pragma once
#include "conf-parser.h"
#include "set.h"
typedef struct Link Link;
typedef struct Network Network;
void network_adjust_dhcp_server(Network *network);
int network_adjust_dhcp_server(Network *network, Set **addresses);
int link_request_dhcp_server_address(Link *link);
int link_request_dhcp_server(Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption);

View File

@@ -20,7 +20,6 @@
#include "networkd-bridge-mdb.h"
#include "networkd-dhcp-common.h"
#include "networkd-dhcp-server-static-lease.h"
#include "networkd-dhcp-server.h"
#include "networkd-ipv6-proxy-ndp.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
@@ -326,8 +325,6 @@ int network_verify(Network *network) {
return r; /* sr_iov_drop_invalid_sections() logs internally. */
network_drop_invalid_static_leases(network);
network_adjust_dhcp_server(network);
return 0;
}

View File

@@ -15,6 +15,7 @@
#include "ipoib.h"
#include "net-condition.h"
#include "netdev.h"
#include "networkd-address.h"
#include "networkd-bridge-vlan.h"
#include "networkd-dhcp-common.h"
#include "networkd-dhcp4.h"
@@ -199,7 +200,8 @@ struct Network {
bool dhcp_server;
bool dhcp_server_bind_to_interface;
unsigned char dhcp_server_address_prefixlen;
struct in_addr dhcp_server_address;
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;

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth-peer
[Network]
IPv6AcceptRA=false
DHCPServer=yes
[DHCPServer]
ServerAddress=0.0.0.0/24
PoolOffset=10
PoolSize=50
DNS=_server_address
NTP=_server_address

View File

@@ -4935,6 +4935,29 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*")
def test_dhcp_server_null_server_address(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network')
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
output = check_output('ip --json address show dev veth-peer')
server_address = json.loads(output)[0]['addr_info'][0]['local']
print(server_address)
output = check_output('ip --json address show dev veth99')
client_address = json.loads(output)[0]['addr_info'][0]['local']
print(client_address)
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth99', env=env)
print(output)
self.assertRegex(output, rf'Address: {client_address} \(DHCP4 via {server_address}\)')
self.assertIn(f'Gateway: {server_address}', output)
self.assertIn(f'DNS: {server_address}', output)
self.assertIn(f'NTP: {server_address}', output)
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env)
self.assertIn(f'Offered DHCP leases: {client_address}', output)
def test_dhcp_server_with_uplink(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network',
'12-dummy.netdev', '25-dhcp-server-uplink.network')