]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkd-dhcp-server.c
network: add NextServer= and Filename= setting to [DHCPServer] section
[thirdparty/systemd.git] / src / network / networkd-dhcp-server.c
index 36707b6e8f0168a7e2548133d5b5b8ab95a747fe..c4eaac3dee37e9efab819cdd53866d023b5776d5 100644 (file)
@@ -9,11 +9,14 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "networkd-address.h"
-#include "networkd-dhcp-server.h"
 #include "networkd-dhcp-server-bus.h"
+#include "networkd-dhcp-server-static-lease.h"
+#include "networkd-dhcp-server.h"
 #include "networkd-link.h"
 #include "networkd-manager.h"
 #include "networkd-network.h"
+#include "networkd-queue.h"
+#include "networkd-route-util.h"
 #include "parse-util.h"
 #include "socket-netlink.h"
 #include "string-table.h"
@@ -55,12 +58,21 @@ void network_adjust_dhcp_server(Network *network) {
                 ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) {
                         if (section_is_invalid(address->section))
                                 continue;
-                        if (address->family == AF_INET &&
-                            !in4_addr_is_localhost(&address->in_addr.in) &&
-                            in4_addr_is_null(&address->in_addr_peer.in)) {
-                                have = true;
-                                break;
-                        }
+
+                        if (address->family != AF_INET)
+                                continue;
+
+                        if (in4_addr_is_localhost(&address->in_addr.in))
+                                continue;
+
+                        if (in4_addr_is_link_local(&address->in_addr.in))
+                                continue;
+
+                        if (in4_addr_is_set(&address->in_addr_peer.in))
+                                continue;
+
+                        have = true;
+                        break;
                 }
                 if (!have) {
                         log_warning("%s: DHCPServer= is enabled, but no static address configured. "
@@ -72,6 +84,40 @@ void network_adjust_dhcp_server(Network *network) {
         }
 }
 
+int link_request_dhcp_server_address(Link *link) {
+        _cleanup_(address_freep) Address *address = NULL;
+        Address *existing;
+        int r;
+
+        assert(link);
+        assert(link->network);
+
+        if (!link_dhcp4_server_enabled(link))
+                return 0;
+
+        if (!in4_addr_is_set(&link->network->dhcp_server_address))
+                return 0;
+
+        r = address_new(&address);
+        if (r < 0)
+                return 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(link, address, &existing) >= 0 &&
+            address_exists(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;
+
+        return link_request_static_address(link, TAKE_PTR(address), true);
+}
+
 static int link_find_dhcp_server_address(Link *link, Address **ret) {
         Address *address;
 
@@ -84,17 +130,47 @@ static int link_find_dhcp_server_address(Link *link, Address **ret) {
                                              link->network->dhcp_server_address_prefixlen, ret);
 
         /* If not, then select one from static addresses. */
-        SET_FOREACH(address, link->static_addresses)
-                if (address->family == AF_INET &&
-                    !in4_addr_is_localhost(&address->in_addr.in) &&
-                    in4_addr_is_null(&address->in_addr_peer.in)) {
-                        *ret = address;
-                        return 0;
-                }
+        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))
+                        continue;
+                if (in4_addr_is_link_local(&address->in_addr.in))
+                        continue;
+                if (in4_addr_is_set(&address->in_addr_peer.in))
+                        continue;
+
+                *ret = address;
+                return 0;
+        }
 
         return -ENOENT;
 }
 
+static int dhcp_server_find_uplink(Link *link, Link **ret) {
+        assert(link);
+
+        if (link->network->dhcp_server_uplink_name)
+                return link_get_by_name(link->manager, link->network->dhcp_server_uplink_name, ret);
+
+        if (link->network->dhcp_server_uplink_index > 0)
+                return link_get_by_index(link->manager, link->network->dhcp_server_uplink_index, ret);
+
+        if (link->network->dhcp_server_uplink_index == UPLINK_INDEX_AUTO) {
+                /* It is not necessary to propagate error in automatic selection. */
+                if (manager_find_uplink(link->manager, AF_INET, link, ret) < 0)
+                        *ret = NULL;
+                return 0;
+        }
+
+        *ret = NULL;
+        return 0;
+}
+
 static int link_push_uplink_to_dhcp_server(
                 Link *link,
                 sd_dhcp_lease_server_type_t what,
@@ -180,7 +256,7 @@ static int link_push_uplink_to_dhcp_server(
                 break;
 
         default:
-                assert_not_reached("Unexpected server type");
+                assert_not_reached();
         }
 
         if (use_dhcp_lease_data && link->dhcp_lease) {
@@ -288,9 +364,10 @@ static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
         return sd_dhcp_server_set_dns(link->dhcp_server, addresses, n_addresses);
 }
 
-int dhcp4_server_configure(Link *link) {
+static int dhcp4_server_configure(Link *link) {
         bool acquired_uplink = false;
         sd_dhcp_option *p;
+        DHCPStaticLease *static_lease;
         Link *uplink = NULL;
         Address *address;
         bool bind_to_interface;
@@ -298,21 +375,18 @@ int dhcp4_server_configure(Link *link) {
 
         assert(link);
 
-        if (!link_dhcp4_server_enabled(link))
-                return 0;
+        log_link_debug(link, "Configuring DHCP Server.");
 
-        if (!(link->flags & IFF_UP))
-                return 0;
+        if (link->dhcp_server)
+                return -EBUSY;
 
-        if (!link->dhcp_server) {
-                r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
-                if (r < 0)
-                        return r;
+        r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
+        if (r < 0)
+                return r;
 
-                r = sd_dhcp_server_attach_event(link->dhcp_server, link->manager->event, 0);
-                if (r < 0)
-                        return r;
-        }
+        r = sd_dhcp_server_attach_event(link->dhcp_server, link->manager->event, 0);
+        if (r < 0)
+                return r;
 
         r = sd_dhcp_server_set_callback(link->dhcp_server, dhcp_server_callback, link);
         if (r < 0)
@@ -328,12 +402,6 @@ int dhcp4_server_configure(Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m");
 
-        /* TODO:
-        r = sd_dhcp_server_set_router(link->dhcp_server, &main_address->in_addr.in);
-        if (r < 0)
-                return r;
-        */
-
         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));
@@ -348,6 +416,14 @@ int dhcp4_server_configure(Link *link) {
                         return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m");
         }
 
+        r = sd_dhcp_server_set_next_server(link->dhcp_server, &link->network->dhcp_server_next_server);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to set next server for DHCPv4 server instance: %m");
+
+        r = sd_dhcp_server_set_filename(link->dhcp_server, link->network->dhcp_server_filename);
+        if (r < 0)
+                return log_link_warning_errno(link, r, "Failed to set filename for DHCPv4 server instance: %m");
+
         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)
@@ -363,7 +439,7 @@ int dhcp4_server_configure(Link *link) {
                 else {
                         /* Emission is requested, but nothing explicitly configured. Let's find a suitable upling */
                         if (!acquired_uplink) {
-                                uplink = manager_find_uplink(link->manager, link);
+                                (void) dhcp_server_find_uplink(link, &uplink);
                                 acquired_uplink = true;
                         }
 
@@ -385,9 +461,11 @@ int dhcp4_server_configure(Link *link) {
                                                dhcp_lease_server_type_to_string(type));
         }
 
-        r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router);
-        if (r < 0)
-                return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
+        if (link->network->dhcp_server_emit_router) {
+                r = sd_dhcp_server_set_router(link->dhcp_server, &link->network->dhcp_server_router);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Failed to set router address for DHCP server: %m");
+        }
 
         r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target);
         if (r < 0)
@@ -439,15 +517,79 @@ int dhcp4_server_configure(Link *link) {
                         return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
         }
 
-        if (!sd_dhcp_server_is_running(link->dhcp_server)) {
-                r = sd_dhcp_server_start(link->dhcp_server);
+        HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) {
+                r = sd_dhcp_server_set_static_lease(link->dhcp_server, &static_lease->address, static_lease->client_id, static_lease->client_id_size);
                 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 log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m");
         }
 
-        return 0;
+        r = sd_dhcp_server_start(link->dhcp_server);
+        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 1;
+}
+
+int link_request_dhcp_server(Link *link) {
+        assert(link);
+
+        if (!link_dhcp4_server_enabled(link))
+                return 0;
+
+        if (link->dhcp_server)
+                return 0;
+
+        log_link_debug(link, "Requesting DHCP server.");
+        return link_queue_request(link, REQUEST_TYPE_DHCP_SERVER, NULL, false, NULL, NULL, NULL);
+}
+
+static bool dhcp_server_is_ready_to_configure(Link *link) {
+        Link *uplink = NULL;
+        Address *a;
+
+        assert(link);
+
+        if (!link->network)
+                return false;
+
+        if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
+                return false;
+
+        if (link->set_flags_messages > 0)
+                return false;
+
+        if (!link_has_carrier(link))
+                return false;
+
+        if (!link->static_addresses_configured)
+                return false;
+
+        if (link_find_dhcp_server_address(link, &a) < 0)
+                return false;
+
+        if (!address_is_ready(a))
+                return false;
+
+        if (dhcp_server_find_uplink(link, &uplink) < 0)
+                return false;
+
+        if (uplink && !uplink->network)
+                return false;
+
+        return true;
+}
+
+int request_process_dhcp_server(Request *req) {
+        assert(req);
+        assert(req->link);
+        assert(req->type == REQUEST_TYPE_DHCP_SERVER);
+
+        if (!dhcp_server_is_ready_to_configure(req->link))
+                return 0;
+
+        return dhcp4_server_configure(req->link);
 }
 
 int config_parse_dhcp_server_relay_agent_suboption(
@@ -500,6 +642,12 @@ int config_parse_dhcp_server_emit(
         assert(emit);
         assert(rvalue);
 
+        if (isempty(rvalue)) {
+                emit->addresses = mfree(emit->addresses);
+                emit->n_addresses = 0;
+                return 0;
+        }
+
         for (const char *p = rvalue;;) {
                 _cleanup_free_ char *w = NULL;
                 union in_addr_union a;
@@ -516,18 +664,26 @@ int config_parse_dhcp_server_emit(
                 if (r == 0)
                         return 0;
 
-                r = in_addr_from_string(AF_INET, w, &a);
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r,
-                                   "Failed to parse %s= address '%s', ignoring: %m", lvalue, w);
-                        continue;
+                if (streq(w, "_server_address"))
+                        a = IN_ADDR_NULL; /* null address will be converted to the server address. */
+                else {
+                        r = in_addr_from_string(AF_INET, w, &a);
+                        if (r < 0) {
+                                log_syntax(unit, LOG_WARNING, filename, line, r,
+                                           "Failed to parse %s= address '%s', ignoring: %m", lvalue, w);
+                                continue;
+                        }
+
+                        if (in4_addr_is_null(&a.in)) {
+                                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                                           "Found a null address in %s=, ignoring.", lvalue);
+                                continue;
+                        }
                 }
 
-                struct in_addr *m = reallocarray(emit->addresses, emit->n_addresses + 1, sizeof(struct in_addr));
-                if (!m)
+                if (!GREEDY_REALLOC(emit->addresses, emit->n_addresses + 1))
                         return log_oom();
 
-                emit->addresses = m;
                 emit->addresses[emit->n_addresses++] = a.in;
         }
 }