diff --git a/NEWS b/NEWS
index 6f4e43da4b3..54c7cdb4876 100644
--- a/NEWS
+++ b/NEWS
@@ -398,6 +398,10 @@ CHANGES WITH 258 in spe:
networkd.conf. (Previously this had to be configured individually in
each .network file.)
+ * PersistLeases= setting in [DHCPServer] section now also accepts
+ "runtime", to make the DHCP server saves and loads bound leases on
+ the runtime storage.
+
* A new Preference= setting has been added to the [IPv6RoutePrefix]
section to configure the route preference field.
diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml
index b053e3182ea..f611e244f5a 100644
--- a/man/networkd.conf.xml
+++ b/man/networkd.conf.xml
@@ -404,7 +404,7 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00
PersistLeases=
Specifies the default value for per-network PersistLeases=.
- Takes a boolean. See for details in
+ Takes a boolean or special value runtime. See for details in
systemd.network5.
Defaults to yes.
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index c3836b0446e..1b4d303fc83 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -443,7 +443,7 @@
Even if this is enabled, the DHCP server will not be started automatically and wait for the
persistent storage being ready to load/save leases in the storage, unless
- RelayTarget= or PersistLeases=no are specified in the
+ RelayTarget= or PersistLeases=no/runtime are specified in the
[DHCPServer] section. It will be started after
systemd-networkd-persistent-storage.service is started, which calls
networkctl persistent-storage yes. See
@@ -4140,16 +4140,24 @@ ServerAddress=192.168.0.1/24
PersistLeases=
- Takes a boolean. When true, the DHCP server will load and save leases in the persistent
- storage. When false, the DHCP server will neither load nor save leases in the persistent storage.
- Hence, bound leases will be lost when the interface is reconfigured e.g. by
+ Takes a boolean or special value runtime. When yes, the
+ DHCP server will load and save leases in the persistent storage. When runtime,
+ the DHCP server will load and save leases in the runtime storage, hence bound leases will be lost
+ when the runtime storage is cleared by e.g. by calling
+ systemctl clean systemd-networkd.service or the system is rebooted. When
+ no, the DHCP server will neither load nor save leases in the persistent storage,
+ hence bound leases will be lost when the interface is reconfigured e.g. by
networkctl reconfigure, or
systemd-networkd.service8
- is restarted. That may cause address conflict on the network. So, please take an extra care when
- disable this setting. When unspecified, the value specified in the same setting in
+ is restarted. Using runtime and no may cause address conflict
+ on the network after the leases are lost. So, please take an extra care when disable this setting.
+ When unspecified, the value specified in the same setting in
networkd.conf5,
which defaults to yes, will be used.
+ When RelayTarget= is specified, this setting will be ignored and no leases
+ will be saved, as there will be no bound lease on the server.
+
diff --git a/network/80-container-ve.network b/network/80-container-ve.network
index 69c534f4e1b..a70a24cbcb1 100644
--- a/network/80-container-ve.network
+++ b/network/80-container-ve.network
@@ -29,4 +29,4 @@ IPv6AcceptRA=no
IPv6SendRA=yes
[DHCPServer]
-PersistLeases=no
+PersistLeases=runtime
diff --git a/network/80-container-vz.network b/network/80-container-vz.network
index 2dc5d87e23b..09eadf48db1 100644
--- a/network/80-container-vz.network
+++ b/network/80-container-vz.network
@@ -28,4 +28,4 @@ IPv6AcceptRA=no
IPv6SendRA=yes
[DHCPServer]
-PersistLeases=no
+PersistLeases=runtime
diff --git a/network/80-namespace-ns-tun.network b/network/80-namespace-ns-tun.network
index fe084838a9b..658eac88d8f 100644
--- a/network/80-namespace-ns-tun.network
+++ b/network/80-namespace-ns-tun.network
@@ -29,4 +29,4 @@ IPv6AcceptRA=no
IPv6SendRA=yes
[DHCPServer]
-PersistLeases=no
+PersistLeases=runtime
diff --git a/network/80-namespace-ns.network b/network/80-namespace-ns.network
index cd1a819973e..2fb422a8bb9 100644
--- a/network/80-namespace-ns.network
+++ b/network/80-namespace-ns.network
@@ -29,4 +29,4 @@ IPv6AcceptRA=no
IPv6SendRA=yes
[DHCPServer]
-PersistLeases=no
+PersistLeases=runtime
diff --git a/network/80-vm-vt.network b/network/80-vm-vt.network
index a7c0f770893..cb968064398 100644
--- a/network/80-vm-vt.network
+++ b/network/80-vm-vt.network
@@ -28,4 +28,4 @@ IPv6AcceptRA=no
IPv6SendRA=yes
[DHCPServer]
-PersistLeases=no
+PersistLeases=runtime
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 099362d1ae3..335f01f1645 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -1605,9 +1605,13 @@ int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char
if (!path_is_safe(path))
return -EINVAL;
- _cleanup_close_ int fd = fd_reopen(dir_fd, O_CLOEXEC | O_DIRECTORY | O_PATH);
- if (fd < 0)
- return fd;
+ _cleanup_close_ int fd = AT_FDCWD; /* Unlike our usual coding style, AT_FDCWD needs to be set,
+ * to pass a 'valid' fd. */
+ if (dir_fd >= 0) {
+ fd = fd_reopen(dir_fd, O_CLOEXEC | O_DIRECTORY | O_PATH);
+ if (fd < 0)
+ return fd;
+ }
r = free_and_strdup(&server->lease_file, path);
if (r < 0)
diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c
index 6679e4ed41d..e36d80e03fc 100644
--- a/src/network/networkd-dhcp-server.c
+++ b/src/network/networkd-dhcp-server.c
@@ -27,6 +27,7 @@
#include "path-util.h"
#include "set.h"
#include "socket-netlink.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@@ -150,13 +151,13 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) {
return 0;
}
-static bool dhcp_server_persist_leases(Link *link) {
+static DHCPServerPersistLeases link_get_dhcp_server_persist_leases(Link *link) {
assert(link);
assert(link->manager);
assert(link->network);
if (in4_addr_is_set(&link->network->dhcp_server_relay_target))
- return false; /* On relay mode. Nothing saved in the persistent storage. */
+ 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;
@@ -164,9 +165,47 @@ static bool dhcp_server_persist_leases(Link *link) {
return link->manager->dhcp_server_persist_leases;
}
+static int link_get_dhcp_server_lease_file(Link *link, int *ret_dir_fd, char **ret_path) {
+ assert(link);
+ assert(link->ifname);
+ assert(ret_dir_fd);
+ assert(ret_path);
+
+ /* This does not copy fd. Do not close fd stored in ret_dir_fd. */
+
+ switch (link_get_dhcp_server_persist_leases(link)) {
+ case DHCP_SERVER_PERSIST_LEASES_NO:
+ *ret_dir_fd = -EBADF;
+ *ret_path = NULL;
+ return 0;
+
+ case DHCP_SERVER_PERSIST_LEASES_YES: {
+ if (link->manager->persistent_storage_fd < 0)
+ return -EBUSY; /* persistent storage is not ready. */
+
+ char *p = path_join("dhcp-server-lease", link->ifname);
+ if (!p)
+ return -ENOMEM;
+
+ *ret_dir_fd = link->manager->persistent_storage_fd;
+ *ret_path = p;
+ return 1;
+ }
+ case DHCP_SERVER_PERSIST_LEASES_RUNTIME: {
+ char *p = path_join("/run/systemd/netif/dhcp-server-lease", link->ifname);
+ if (!p)
+ return -ENOMEM;
+
+ *ret_dir_fd = AT_FDCWD;
+ *ret_path = p;
+ return 1;
+ }
+ default:
+ assert_not_reached();
+ }
+}
+
int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret) {
- struct in_addr a;
- uint8_t prefixlen;
int r;
assert(link);
@@ -185,18 +224,18 @@ int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *addr
if (!link_dhcp4_server_enabled(link))
return -ENOENT;
- if (!dhcp_server_persist_leases(link))
+ _cleanup_free_ char *lease_file = NULL;
+ int dir_fd;
+ r = link_get_dhcp_server_lease_file(link, &dir_fd, &lease_file);
+ if (r < 0)
+ return r;
+ if (r == 0) /* persistent leases is disabled */
return -ENOENT;
- if (link->manager->persistent_storage_fd < 0)
- return -EBUSY; /* The persistent storage is not ready, try later again. */
-
- _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname);
- if (!lease_file)
- return -ENOMEM;
-
+ struct in_addr a;
+ uint8_t prefixlen;
r = dhcp_server_leases_file_get_server_address(
- link->manager->persistent_storage_fd,
+ dir_fd,
lease_file,
&a,
&prefixlen);
@@ -234,16 +273,15 @@ int link_start_dhcp4_server(Link *link) {
/* TODO: Maybe, also check the system time is synced. If the system does not have RTC battery, then
* the realtime clock in not usable in the early boot stage, and all saved leases may be wrongly
* handled as expired and dropped. */
- if (dhcp_server_persist_leases(link)) {
-
- if (link->manager->persistent_storage_fd < 0)
- return 0; /* persistent storage is not ready. */
-
- _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname);
- if (!lease_file)
- return -ENOMEM;
-
- r = sd_dhcp_server_set_lease_file(link->dhcp_server, link->manager->persistent_storage_fd, lease_file);
+ _cleanup_free_ char *lease_file = NULL;
+ int dir_fd;
+ r = link_get_dhcp_server_lease_file(link, &dir_fd, &lease_file);
+ if (r == -EBUSY)
+ return 0; /* persistent storage is not ready. */
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ r = sd_dhcp_server_set_lease_file(link->dhcp_server, dir_fd, lease_file);
if (r < 0)
return r;
}
@@ -265,7 +303,7 @@ void manager_toggle_dhcp4_server_state(Manager *manager, bool start) {
HASHMAP_FOREACH(link, manager->links_by_index) {
if (!link->dhcp_server)
continue;
- if (!dhcp_server_persist_leases(link))
+ if (link_get_dhcp_server_persist_leases(link) != DHCP_SERVER_PERSIST_LEASES_YES)
continue;
/* Even if 'start' is true, first we need to stop the server. Otherwise, we cannot (re)set
@@ -916,3 +954,19 @@ int config_parse_dhcp_server_ipv6_only_preferred(
*usec = t;
return 0;
}
+
+static const char* const dhcp_server_persist_leases_table[_DHCP_SERVER_PERSIST_LEASES_MAX] = {
+ [DHCP_SERVER_PERSIST_LEASES_NO] = "no",
+ [DHCP_SERVER_PERSIST_LEASES_YES] = "yes",
+ [DHCP_SERVER_PERSIST_LEASES_RUNTIME] = "runtime",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(
+ dhcp_server_persist_leases,
+ DHCPServerPersistLeases,
+ DHCP_SERVER_PERSIST_LEASES_YES);
+
+DEFINE_CONFIG_PARSE_ENUM(
+ config_parse_dhcp_server_persist_leases,
+ dhcp_server_persist_leases,
+ DHCPServerPersistLeases);
diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h
index 419088f3066..e46ad3a8579 100644
--- a/src/network/networkd-dhcp-server.h
+++ b/src/network/networkd-dhcp-server.h
@@ -3,6 +3,14 @@
#include "networkd-forward.h"
+typedef enum DHCPServerPersistLeases {
+ DHCP_SERVER_PERSIST_LEASES_NO,
+ DHCP_SERVER_PERSIST_LEASES_YES,
+ DHCP_SERVER_PERSIST_LEASES_RUNTIME,
+ _DHCP_SERVER_PERSIST_LEASES_MAX,
+ _DHCP_SERVER_PERSIST_LEASES_INVALID = -EINVAL,
+} DHCPServerPersistLeases;
+
int network_adjust_dhcp_server(Network *network, Set **addresses);
int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret);
int link_request_dhcp_server(Link *link);
@@ -14,3 +22,4 @@ 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);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_persist_leases);
diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf
index 2eb847d119e..3684a696ff8 100644
--- a/src/network/networkd-gperf.gperf
+++ b/src/network/networkd-gperf.gperf
@@ -44,7 +44,7 @@ DHCPv4.DUIDRawData, config_parse_duid_rawdata,
DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp6_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_bool, 0, offsetof(Manager, dhcp_server_persist_leases)
+DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Manager, dhcp_server_persist_leases)
/* Deprecated */
DHCP.DUIDType, config_parse_manager_duid_type, 0, 0
DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index fad4d82183d..a0455544cf4 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -635,7 +635,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_server_persist_leases = true,
+ .dhcp_server_persist_leases = DHCP_SERVER_PERSIST_LEASES_YES,
.serialization_fd = -EBADF,
.ip_forwarding = { -1, -1, },
#if HAVE_VMLINUX_H
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index db669d170d2..d0897e76ad0 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -36,7 +36,7 @@ typedef struct Manager {
bool manage_foreign_routes;
bool manage_foreign_rules;
bool manage_foreign_nexthops;
- bool dhcp_server_persist_leases;
+ DHCPServerPersistLeases dhcp_server_persist_leases;
Set *dirty_links;
Set *new_wlan_ifindices;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 36b833ef369..577dccc9eb0 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -389,7 +389,7 @@ DHCPServer.BootServerAddress, config_parse_in_addr_non_null,
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)
DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit)
-DHCPServer.PersistLeases, config_parse_tristate, 0, offsetof(Network, dhcp_server_persist_leases)
+DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Network, dhcp_server_persist_leases)
DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0
DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0
Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 69fa1983da4..961b431a68e 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -435,7 +435,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_server_emit_router = true,
.dhcp_server_emit_timezone = true,
.dhcp_server_rapid_commit = true,
- .dhcp_server_persist_leases = -1,
+ .dhcp_server_persist_leases = _DHCP_SERVER_PERSIST_LEASES_INVALID,
.router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC,
.router_dns_lifetime_usec = RADV_DEFAULT_VALID_LIFETIME_USEC,
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 51c9160760f..0e14cfa7b5b 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -11,6 +11,7 @@
#include "network-util.h"
#include "networkd-bridge-vlan.h"
#include "networkd-dhcp-common.h"
+#include "networkd-dhcp-server.h"
#include "networkd-dhcp4.h"
#include "networkd-dhcp6.h"
#include "networkd-dns.h"
@@ -228,7 +229,7 @@ typedef struct Network {
char *dhcp_server_boot_filename;
usec_t dhcp_server_ipv6_only_preferred_usec;
bool dhcp_server_rapid_commit;
- int dhcp_server_persist_leases;
+ DHCPServerPersistLeases dhcp_server_persist_leases;
/* link-local addressing support */
AddressFamily link_local;
diff --git a/src/network/networkd.c b/src/network/networkd.c
index dda1a4cf267..29ee7c52b3c 100644
--- a/src/network/networkd.c
+++ b/src/network/networkd.c
@@ -69,8 +69,9 @@ static int run(int argc, char *argv[]) {
/* Always create the directories people can create inotify watches in. It is necessary to create the
* following subdirectories after drop_privileges() to make them owned by systemd-network. */
FOREACH_STRING(p,
- "/run/systemd/netif/links/",
- "/run/systemd/netif/leases/") {
+ "/run/systemd/netif/dhcp-server-lease/",
+ "/run/systemd/netif/leases/",
+ "/run/systemd/netif/links/") {
r = mkdir_safe_label(p, 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE);
if (r < 0)
log_warning_errno(r, "Could not create directory '%s': %m", p);
diff --git a/test/test-network/conf/persist-leases-runtime.conf b/test/test-network/conf/persist-leases-runtime.conf
new file mode 100644
index 00000000000..6f1b7374c32
--- /dev/null
+++ b/test/test-network/conf/persist-leases-runtime.conf
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[DHCPServer]
+PersistLeases=runtime
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 99a91bed761..7f20aa4aa53 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -7110,7 +7110,7 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
def tearDown(self):
tear_down_common()
- def check_dhcp_server(self, persist_leases=True):
+ def check_dhcp_server(self, persist_leases='yes'):
output = networkctl_status('veth99')
print(output)
self.assertRegex(output, r'Address: 192.168.5.[0-9]* \(DHCPv4 via 192.168.5.1\)')
@@ -7122,11 +7122,16 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*")
- if persist_leases:
- with open('/var/lib/systemd/network/dhcp-server-lease/veth-peer', encoding='utf-8') as f:
- check_json(f.read())
+ if persist_leases == 'yes':
+ path = '/var/lib/systemd/network/dhcp-server-lease/veth-peer'
+ elif persist_leases == 'runtime':
+ path = '/run/systemd/netif/dhcp-server-lease/veth-peer'
else:
- self.assertFalse(os.path.exists('/var/lib/systemd/network/dhcp-server-lease/veth-peer'))
+ path = None
+
+ if path:
+ with open(path, encoding='utf-8') as f:
+ check_json(f.read())
def test_dhcp_server(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
@@ -7152,7 +7157,7 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
start_networkd()
self.wait_online('veth99:routable', 'veth-peer:routable')
- self.check_dhcp_server(persist_leases=False)
+ self.check_dhcp_server(persist_leases='no')
remove_networkd_conf_dropin('persist-leases-no.conf')
with open(os.path.join(network_unit_dir, '25-dhcp-server.network'), mode='a', encoding='utf-8') as f:
@@ -7160,7 +7165,23 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
restart_networkd()
self.wait_online('veth99:routable', 'veth-peer:routable')
- self.check_dhcp_server(persist_leases=False)
+ self.check_dhcp_server(persist_leases='no')
+
+ def test_dhcp_server_persist_leases_runtime(self):
+ copy_networkd_conf_dropin('persist-leases-runtime.conf')
+ copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
+ start_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ self.check_dhcp_server(persist_leases='runtime')
+
+ remove_networkd_conf_dropin('persist-leases-runtime.conf')
+ with open(os.path.join(network_unit_dir, '25-dhcp-server.network'), mode='a', encoding='utf-8') as f:
+ f.write('[DHCPServer]\nPersistLeases=runtime')
+ restart_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ self.check_dhcp_server(persist_leases='runtime')
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')