]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-dhcp-server.c
network: introduce link_get_use_ntp()
[thirdparty/systemd.git] / src / network / networkd-dhcp-server.c
index fedc2e4f04d83842fdac4dfdfd6d1132dfc39ebd..c35102af74a5ff97c4cecd3c872b0183e132cb58 100644 (file)
@@ -1,14 +1,16 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <netinet/in.h>
-#include <net/if.h>
-#include <linux/if_arp.h> /* Must be included after <net/if.h> */
-#include <linux/if.h> /* Must be included after <net/if.h> */
+#include <linux/if_arp.h>
+#include <linux/if.h>
 
 #include "sd-dhcp-server.h"
 
+#include "dhcp-protocol.h"
+#include "dhcp-server-lease-internal.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "network-common.h"
 #include "networkd-address.h"
 #include "networkd-dhcp-server-bus.h"
 #include "networkd-dhcp-server-static-lease.h"
 #include "networkd-link.h"
 #include "networkd-manager.h"
 #include "networkd-network.h"
+#include "networkd-ntp.h"
 #include "networkd-queue.h"
 #include "networkd-route-util.h"
 #include "parse-util.h"
+#include "path-util.h"
 #include "socket-netlink.h"
 #include "string-table.h"
 #include "string-util.h"
@@ -39,26 +43,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;
@@ -72,84 +81,204 @@ 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;
+                        address->used_by_dhcp_server = true;
                         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;
+                }
+
+        } else {
+                _cleanup_(address_unrefp) Address *a = NULL;
+                Address *existing;
+                unsigned line;
+
+                /* TODO: check if the prefix length is small enough for the pool. */
+
+                /* If an address is explicitly specified, then check if the corresponding [Address] section
+                 * is configured, and add one if not. */
+
+                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 = 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);
+
+                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);
+
+                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);
+                a->used_by_dhcp_server = true;
+
+                r = address_section_verify(a);
+                if (r < 0)
+                        return r;
+
+                r = set_ensure_put(addresses, &address_hash_ops, a);
+                if (r < 0)
+                        return log_oom();
+                assert(r > 0);
+
+                network->dhcp_server_address = TAKE_PTR(a);
         }
+
+        return 0;
+}
+
+static bool 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. */
+
+        if (link->network->dhcp_server_persist_leases >= 0)
+                return link->network->dhcp_server_persist_leases;
+
+        return link->manager->dhcp_server_persist_leases;
 }
 
-int link_request_dhcp_server_address(Link *link) {
-        _cleanup_(address_freep) Address *address = NULL;
-        Address *existing;
+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);
-        assert(link->network);
+        assert(link->manager);
+        assert(address);
+        assert(ret);
+
+        /* If the DHCP server address is configured as a null address, reuse the server address of the
+         * previous instance. */
+        if (address->family != AF_INET)
+                return -ENOENT;
+
+        if (!address->used_by_dhcp_server)
+                return -ENOENT;
 
         if (!link_dhcp4_server_enabled(link))
-                return 0;
+                return -ENOENT;
 
-        if (!in4_addr_is_set(&link->network->dhcp_server_address))
-                return 0;
+        if (!dhcp_server_persist_leases(link))
+                return -ENOENT;
 
-        r = address_new(&address);
-        if (r < 0)
+        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;
+
+        r = dhcp_server_leases_file_get_server_address(
+                        link->manager->persistent_storage_fd,
+                        lease_file,
+                        &a,
+                        &prefixlen);
+        if (r == -ENOENT)
                 return r;
+        if (r < 0)
+                return log_warning_errno(r, "Failed to load lease file %s: %s",
+                                         lease_file,
+                                         r == -ENXIO ? "expected JSON content not found" :
+                                         r == -EINVAL ? "invalid JSON" :
+                                         STRERROR(r));
 
-        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;
-        address_set_broadcast(address, link);
-
-        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;
+        if (prefixlen != address->prefixlen)
+                return -ENOENT;
 
-        return link_request_static_address(link, address);
+        ret->in = a;
+        return 0;
 }
 
-static int link_find_dhcp_server_address(Link *link, Address **ret) {
-        Address *address;
+int link_start_dhcp4_server(Link *link) {
+        int r;
 
         assert(link);
-        assert(link->network);
+        assert(link->manager);
 
-        /* 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 (!link->dhcp_server)
+                return 0; /* Not configured yet. */
 
-        /* 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))
+        if (!link_has_carrier(link))
+                return 0;
+
+        if (sd_dhcp_server_is_running(link->dhcp_server))
+                return 0; /* already started. */
+
+        /* 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);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_dhcp_server_start(link->dhcp_server);
+        if (r < 0)
+                return r;
+
+        log_link_debug(link, "Offering DHCPv4 leases");
+        return 0;
+}
+
+void manager_toggle_dhcp4_server_state(Manager *manager, bool start) {
+        Link *link;
+        int r;
+
+        assert(manager);
+
+        HASHMAP_FOREACH(link, manager->links_by_index) {
+                if (!link->dhcp_server)
                         continue;
-                if (in4_addr_is_link_local(&address->in_addr.in))
+                if (!dhcp_server_persist_leases(link))
                         continue;
-                if (in4_addr_is_set(&address->in_addr_peer.in))
+
+                /* Even if 'start' is true, first we need to stop the server. Otherwise, we cannot (re)set
+                 * the lease file in link_start_dhcp4_server(). */
+                r = sd_dhcp_server_stop(link->dhcp_server);
+                if (r < 0)
+                        log_link_debug_errno(link, r, "Failed to stop DHCP server, ignoring: %m");
+
+                if (!start)
                         continue;
 
-                *ret = address;
-                return 0;
+                r = link_start_dhcp4_server(link);
+                if (r < 0)
+                        log_link_debug_errno(link, r, "Failed to start DHCP server, ignoring: %m");
         }
-
-        return -ENOENT;
 }
 
 static int dhcp_server_find_uplink(Link *link, Link **ret) {
@@ -214,7 +343,7 @@ static int link_push_uplink_to_dhcp_server(
                         addresses[n_addresses++] = ia;
                 }
 
-                use_dhcp_lease_data = link->network->dhcp_use_dns;
+                use_dhcp_lease_data = link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4);
                 break;
 
         case SD_DHCP_LEASE_NTP: {
@@ -237,7 +366,7 @@ static int link_push_uplink_to_dhcp_server(
                         addresses[n_addresses++] = ia.in;
                 }
 
-                use_dhcp_lease_data = link->network->dhcp_use_ntp;
+                use_dhcp_lease_data = link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4);
                 break;
         }
 
@@ -334,19 +463,17 @@ static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
         for (;;) {
                 _cleanup_free_ char *line = NULL;
                 const char *a;
-                char *l;
 
-                r = read_line(f, LONG_LINE_MAX, &line);
+                r = read_stripped_line(f, LONG_LINE_MAX, &line);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read " PRIVATE_UPLINK_RESOLV_CONF ": %m");
                 if (r == 0)
                         break;
 
-                l = strstrip(line);
-                if (IN_SET(*l, '#', ';', 0))
+                if (IN_SET(*line, '#', ';', 0))
                         continue;
 
-                a = first_word(l, "nameserver");
+                a = first_word(line, "nameserver");
                 if (!a)
                         continue;
 
@@ -371,6 +498,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.");
 
@@ -389,7 +518,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");
 
@@ -400,19 +529,21 @@ static int dhcp4_server_configure(Link *link) {
                 return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m");
 
         if (link->network->dhcp_server_max_lease_time_usec > 0) {
-                r = sd_dhcp_server_set_max_lease_time(link->dhcp_server,
-                                                      DIV_ROUND_UP(link->network->dhcp_server_max_lease_time_usec, USEC_PER_SEC));
+                r = sd_dhcp_server_set_max_lease_time(link->dhcp_server, link->network->dhcp_server_max_lease_time_usec);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Failed to set maximum lease time for DHCPv4 server instance: %m");
         }
 
         if (link->network->dhcp_server_default_lease_time_usec > 0) {
-                r = sd_dhcp_server_set_default_lease_time(link->dhcp_server,
-                                                          DIV_ROUND_UP(link->network->dhcp_server_default_lease_time_usec, USEC_PER_SEC));
+                r = sd_dhcp_server_set_default_lease_time(link->dhcp_server, link->network->dhcp_server_default_lease_time_usec);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m");
         }
 
+        r = sd_dhcp_server_set_ipv6_only_preferred_usec(link->dhcp_server, link->network->dhcp_server_ipv6_only_preferred_usec);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to set IPv6 only preferred time for DHCPv4 server instance: %m");
+
         r = sd_dhcp_server_set_boot_server_address(link->dhcp_server, &link->network->dhcp_server_boot_server_address);
         if (r < 0)
                 return log_link_warning_errno(link, r, "Failed to set boot server address for DHCPv4 server instance: %m");
@@ -425,7 +556,12 @@ static int dhcp4_server_configure(Link *link) {
         if (r < 0)
                 return log_link_warning_errno(link, r, "Failed to set boot filename for DHCPv4 server instance: %m");
 
-        for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) {
+        r = sd_dhcp_server_set_rapid_commit(link->dhcp_server, link->network->dhcp_server_rapid_commit);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to %s Rapid Commit support for DHCPv4 server instance: %m",
+                                              enable_disable(link->network->dhcp_server_rapid_commit));
+
+        for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type++) {
 
                 if (!link->network->dhcp_server_emit[type].emit)
                         continue;
@@ -524,11 +660,10 @@ static int dhcp4_server_configure(Link *link) {
                         return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m");
         }
 
-        r = sd_dhcp_server_start(link->dhcp_server);
+        r = link_start_dhcp4_server(link);
         if (r < 0)
                 return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m");
 
-        log_link_debug(link, "Offering DHCPv4 leases");
         return 0;
 }
 
@@ -537,6 +672,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;
@@ -547,7 +684,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))
@@ -713,7 +850,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;
         }
@@ -724,14 +861,56 @@ 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;
 }
+
+int config_parse_dhcp_server_ipv6_only_preferred(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        usec_t t, *usec = ASSERT_PTR(data);
+        int r;
+
+        assert(filename);
+        assert(section);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                *usec = 0;
+                return 0;
+        }
+
+        r = parse_sec(rvalue, &t);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
+                return 0;
+        }
+
+        if (t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled()) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
+                return 0;
+        }
+
+        *usec = t;
+        return 0;
+}