diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 0532007010..62adac34e1 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -3277,12 +3277,43 @@ Token=prefixstable:2002:da8:1::
ServerAddress=
- 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.
+
+ Specifies the server address for the DHCP server. Takes an IPv4 address with prefix length
+ separated with a slash, e.g. 192.168.0.1/24. 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.
+ This implies Address= in [Network] or [Address] section with the same
+ address and prefix length. That is,
+ [Network]
+DHCPServer=yes
+Address=192.168.0.1/24
+Address=192.168.0.2/24
+[DHCPServer]
+ServerAddress=192.168.0.1/24
+ or
+ [Network]
+DHCPServer=yes
+[Address]
+Address=192.168.0.1/24
+[Address]
+Address=192.168.0.2/24
+[DHCPServer]
+ServerAddress=192.168.0.1/24
+ are equivalent to the following.
+ [Network]
+DHCPServer=yes
+Address=192.168.0.2/24
+[DHCPServer]
+ServerAddress=192.168.0.1/24
+
+ Since version 255, like the Address= setting in [Network] or [Address]
+ section, this also supports a null address, e.g. 0.0.0.0/24, and an unused
+ address will be automatically selected. For more details about the automatic address selection,
+ see Address= setting in [Network] section in the above.
-
+
+
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 048f288df8..c43655f57a 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -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;
}
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index 87c7d36486..35acb81aa3 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -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);
diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c
index 240c512fd8..0857311d5e 100644
--- a/src/network/networkd-dhcp-server.c
+++ b/src/network/networkd-dhcp-server.c
@@ -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;
}
diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h
index cb2a8b6a34..4fd4429deb 100644
--- a/src/network/networkd-dhcp-server.h
+++ b/src/network/networkd-dhcp-server.h
@@ -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);
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 7feb178c80..e0a8be6b54 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -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;
}
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 98cc1f5abc..34bc179d75 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -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;
diff --git a/test/test-network/conf/25-dhcp-server-null-server-address.network b/test/test-network/conf/25-dhcp-server-null-server-address.network
new file mode 100644
index 0000000000..fff83f4c27
--- /dev/null
+++ b/test/test-network/conf/25-dhcp-server-null-server-address.network
@@ -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
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 90d68b54db..9d1b273f95 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -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')