]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network/dhcp-server: save and load leases in runtime directory when PersistLeases...
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 13 Jun 2025 18:41:20 +0000 (03:41 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 13 Jun 2025 18:41:20 +0000 (03:41 +0900)
With 9ccc369ff30138b9c2cf3ed8faf28d8fe42f8377, 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.

man/networkd.conf.xml
man/systemd.network.xml
src/network/networkd-dhcp-server.c
src/network/networkd-dhcp-server.h
src/network/networkd-gperf.gperf
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd.c

index b053e3182eaa9c89cb285ba15cddaf0c179bd0e6..f611e244f5aaf831120334418f7fc8ea0a697772 100644 (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>
 
index c3836b0446eaa4026784c0283e62c074861c39ab..1b4d303fc83b32a4f03cb128a133f2a34ffabb25 100644 (file)
 
           <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>
index 6679e4ed41dcac631395c5049e37fb1a44be6d1b..e36d80e03fcd7efafbbb70be6c8c3c998dc9bef7 100644 (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);
index 419088f3066c5d4e867faeace45e8d89855d2b30..e46ad3a8579af08a9fc6431696fa833044fa87c9 100644 (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);
index 2eb847d119e97e2459b4f759fc8243769d48d41b..3684a696ff89e56a7a85a1e781efd4e77cebc4b8 100644 (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
index fad4d82183d5be35b81776fdb380a0d2a4a8dd1a..a0455544cf46091a251e0fb8c85cf179ebd9a63c 100644 (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
index db669d170d291021ad4abcf373749849b82c27ba..d0897e76ad09c6b86d6f8fdbadaa97be59f24490 100644 (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;
index 36b833ef369b6806099db223f646cea35b742bf5..577dccc9eb020d30933d8884613f00885191b6d1 100644 (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)
index 69fa1983da4d793c5c180f2cf560a819115fb88a..961b431a68e66e3bbfae14ceea3c9075b9b7e6db 100644 (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,
index 51c9160760fb595a609cd60bc7d3eeaf42c2acb2..0e14cfa7b5b51e87acea4f2820add111a0a9ab8c 100644 (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;
index dda1a4cf267b042ecad6a3051f93921f179dcbb5..29ee7c52b3cc1f7882ff7d95819b04f024336e8e 100644 (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);