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