]> 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 64bb23f3870b842108a977fa57d366e2f9fcc48c..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"
@@ -29,33 +32,143 @@ static bool link_dhcp4_server_enabled(Link *link) {
         if (!link->network)
                 return false;
 
-        if (link->network->bond)
-                return false;
-
         if (link->iftype == ARPHRD_CAN)
                 return false;
 
         return link->network->dhcp_server;
 }
 
-static Address* link_find_dhcp_server_address(Link *link) {
+void network_adjust_dhcp_server(Network *network) {
+        assert(network);
+
+        if (!network->dhcp_server)
+                return;
+
+        if (network->bond) {
+                log_warning("%s: DHCPServer= is enabled for bond slave. Disabling DHCP server.",
+                            network->filename);
+                network->dhcp_server = false;
+                return;
+        }
+
+        if (!in4_addr_is_set(&network->dhcp_server_address)) {
+                Address *address;
+                bool have = false;
+
+                ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) {
+                        if (section_is_invalid(address->section))
+                                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;
+
+                        have = true;
+                        break;
+                }
+                if (!have) {
+                        log_warning("%s: DHCPServer= is enabled, but no static address configured. "
+                                    "Disabling DHCP server.",
+                                    network->filename);
+                        network->dhcp_server = false;
+                        return;
+                }
+        }
+}
+
+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;
 
         assert(link);
         assert(link->network);
 
-        /* The first statically configured address if there is any */
-        ORDERED_HASHMAP_FOREACH(address, link->network->addresses_by_section)
-                if (address->family == AF_INET &&
-                    in_addr_is_set(address->family, &address->in_addr))
-                        return address;
+        /* 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 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))
+                        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 that didn't work, find a suitable address we got from the pool */
-        SET_FOREACH(address, link->pool_addresses)
-                if (address->family == AF_INET)
-                        return address;
+        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;
+        }
 
-        return NULL;
+        *ret = NULL;
+        return 0;
 }
 
 static int link_push_uplink_to_dhcp_server(
@@ -64,8 +177,8 @@ static int link_push_uplink_to_dhcp_server(
                 sd_dhcp_server *s) {
 
         _cleanup_free_ struct in_addr *addresses = NULL;
-        size_t n_addresses = 0, n_allocated = 0;
         bool use_dhcp_lease_data = true;
+        size_t n_addresses = 0;
 
         assert(link);
 
@@ -94,7 +207,7 @@ static int link_push_uplink_to_dhcp_server(
                         if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
                                 continue;
 
-                        if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
+                        if (!GREEDY_REALLOC(addresses, n_addresses + 1))
                                 return log_oom();
 
                         addresses[n_addresses++] = ia;
@@ -119,7 +232,7 @@ static int link_push_uplink_to_dhcp_server(
                         if (in4_addr_is_null(&ia.in) || in4_addr_is_localhost(&ia.in))
                                 continue;
 
-                        if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
+                        if (!GREEDY_REALLOC(addresses, n_addresses + 1))
                                 return log_oom();
 
                         addresses[n_addresses++] = ia.in;
@@ -143,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) {
@@ -151,7 +264,7 @@ static int link_push_uplink_to_dhcp_server(
 
                 int n = sd_dhcp_lease_get_servers(link->dhcp_lease, what, &da);
                 if (n > 0) {
-                        if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
+                        if (!GREEDY_REALLOC(addresses, n_addresses + n))
                                 return log_oom();
 
                         for (int j = 0; j < n; j++)
@@ -166,7 +279,11 @@ static int link_push_uplink_to_dhcp_server(
         return sd_dhcp_server_set_servers(s, what, addresses, n_addresses);
 }
 
-static int dhcp4_server_parse_dns_server_string_and_warn(Link *l, const char *string, struct in_addr **addresses, size_t *n_allocated, size_t *n_addresses) {
+static int dhcp4_server_parse_dns_server_string_and_warn(
+                const char *string,
+                struct in_addr **addresses,
+                size_t *n_addresses) {
+
         for (;;) {
                 _cleanup_free_ char *word = NULL, *server_name = NULL;
                 union in_addr_union address;
@@ -192,7 +309,7 @@ static int dhcp4_server_parse_dns_server_string_and_warn(Link *l, const char *st
                 if (in4_addr_is_null(&address.in) || in4_addr_is_localhost(&address.in))
                         continue;
 
-                if (!GREEDY_REALLOC(*addresses, *n_allocated, *n_addresses + 1))
+                if (!GREEDY_REALLOC(*addresses, *n_addresses + 1))
                         return log_oom();
 
                 (*addresses)[(*n_addresses)++] = address.in;
@@ -203,8 +320,8 @@ static int dhcp4_server_parse_dns_server_string_and_warn(Link *l, const char *st
 
 static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
         _cleanup_free_ struct in_addr *addresses = NULL;
-        size_t n_addresses = 0, n_allocated = 0;
         _cleanup_fclose_ FILE *f = NULL;
+        size_t n_addresses = 0;
         int n = 0, r;
 
         f = fopen(PRIVATE_UPLINK_RESOLV_CONF, "re");
@@ -236,7 +353,7 @@ static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
                 if (!a)
                         continue;
 
-                r = dhcp4_server_parse_dns_server_string_and_warn(link, a, &addresses, &n_allocated, &n_addresses);
+                r = dhcp4_server_parse_dns_server_string_and_warn(a, &addresses, &n_addresses);
                 if (r < 0)
                         log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
         }
@@ -247,39 +364,37 @@ 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;
         int r;
 
         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)
                 return log_link_warning_errno(link, r, "Failed to set callback for DHCPv4 server instance: %m");
 
-        address = link_find_dhcp_server_address(link);
-        if (!address)
-                return log_link_error_errno(link, SYNTHETIC_ERRNO(EBUSY),
-                                            "Failed to find suitable address for DHCPv4 server instance.");
+        r = link_find_dhcp_server_address(link, &address);
+        if (r < 0)
+                return log_link_error_errno(link, r, "Failed to find suitable address for DHCPv4 server instance: %m");
 
         /* use the server address' subnet as the pool */
         r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen,
@@ -287,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));
@@ -307,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)
@@ -322,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;
                         }
 
@@ -344,31 +461,44 @@ int dhcp4_server_configure(Link *link) {
                                                dhcp_lease_server_type_to_string(type));
         }
 
-        r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, link->network->dhcp_server_bind_to_interface);
+        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)
+                return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m");
+
+        bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface;
+        r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface);
         if (r < 0)
                 return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m");
 
-        r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router);
+        r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id);
         if (r < 0)
-                return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
+                return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m");
 
         if (link->network->dhcp_server_emit_timezone) {
                 _cleanup_free_ char *buffer = NULL;
-                const char *tz;
+                const char *tz = NULL;
 
                 if (link->network->dhcp_server_timezone)
                         tz = link->network->dhcp_server_timezone;
                 else {
                         r = get_timezone(&buffer);
                         if (r < 0)
-                                return log_link_error_errno(link, r, "Failed to determine timezone: %m");
-
-                        tz = buffer;
+                                log_link_warning_errno(link, r, "Failed to determine timezone, not sending timezone: %m");
+                        else
+                                tz = buffer;
                 }
 
-                r = sd_dhcp_server_set_timezone(link->dhcp_server, tz);
-                if (r < 0)
-                        return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m");
+                if (tz) {
+                        r = sd_dhcp_server_set_timezone(link->dhcp_server, tz);
+                        if (r < 0)
+                                return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m");
+                }
         }
 
         ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) {
@@ -387,15 +517,112 @@ 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");
+                        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);
+        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(
+                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) {
+
+        char **suboption_value = data;
+        char* p;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
 
-                log_link_debug(link, "Offering DHCPv4 leases");
+        if (isempty(rvalue)) {
+                *suboption_value = mfree(*suboption_value);
+                return 0;
         }
 
-        return 0;
+        p = startswith(rvalue, "string:");
+        if (!p) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue);
+                return 0;
+        }
+        return free_and_strdup(suboption_value, empty_to_null(p));
 }
 
 int config_parse_dhcp_server_emit(
@@ -415,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;
@@ -431,18 +664,71 @@ 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;
         }
 }
+
+int config_parse_dhcp_server_address(
+                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) {
+
+        Network *network = userdata;
+        union in_addr_union a;
+        unsigned char prefixlen;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                network->dhcp_server_address = (struct in_addr) {};
+                network->dhcp_server_address_prefixlen = 0;
+                return 0;
+        }
+
+        r = in_addr_prefix_from_string(rvalue, AF_INET, &a, &prefixlen);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+                return 0;
+        }
+        if (in4_addr_is_null(&a.in) || in4_addr_is_localhost(&a.in)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "DHCP server address cannot be the ANY address or a localhost address, "
+                           "ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        network->dhcp_server_address = a.in;
+        network->dhcp_server_address_prefixlen = prefixlen;
+        return 0;
+}