network/dhcp-server: save and load leases in runtime directory when PersistLeases=runtime

With 9ccc369ff3, PersistLeases= is
disabled on the host side virtual interfaces for containers.

However, even it is not necessary to save the leases for containers
on a persistent storage, still we should save them on somewhere.
Otherwise, leases will be lost when networkd on the host is restarted
or the host side interface is reconfigured.

This introduce PersistLeases=runtime to save and load leases on runtime
storage.
This commit is contained in:
Yu Watanabe
2025-06-14 03:41:20 +09:00
parent 98b02dd7e7
commit a145343b90
11 changed files with 112 additions and 39 deletions

View File

@@ -404,7 +404,7 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00</programlisting>
<term><varname>PersistLeases=</varname></term>
<listitem>
<para>Specifies the default value for per-network <varname>PersistLeases=</varname>.
Takes a boolean. See for details in
Takes a boolean or special value <literal>runtime</literal>. See for details in
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Defaults to <literal>yes</literal>.</para>

View File

@@ -443,7 +443,7 @@
<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</varname> are specified in the
<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
@@ -4140,16 +4140,24 @@ ServerAddress=192.168.0.1/24</programlisting>
<varlistentry>
<term><varname>PersistLeases=</varname></term>
<listitem>
<para>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
<para>Takes a boolean or special value <literal>runtime</literal>. When <literal>yes</literal>, the
DHCP server will load and save leases in the persistent storage. When <literal>runtime</literal>,
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
<command>systemctl clean systemd-networkd.service</command> or the system is rebooted. When
<literal>no</literal>, 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
<command>networkctl reconfigure</command>, or
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
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 <literal>runtime</literal> and <literal>no</literal> 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
<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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);