From 0017ba3165f69e8afb7f73127281bb9a7e5b5641 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 14 May 2021 20:27:33 +0900 Subject: [PATCH] network: dhcp-server: introduce ServerAddress= setting This may be useful when the link which DHCP server running on has multiple static addresses. --- man/systemd.network.xml | 8 ++ src/network/networkd-address.c | 26 ++++ src/network/networkd-dhcp-server.c | 118 +++++++++++++++--- src/network/networkd-dhcp-server.h | 6 +- src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 2 + src/network/networkd-network.h | 3 +- .../fuzz-network-parser/directives.network | 1 + 8 files changed, 143 insertions(+), 22 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index fe330be0be..02e464b193 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2344,6 +2344,14 @@ IPv6Token=prefixstable:2002:da8:1:: + + ServerAddress= + Specifies server address for the DHCP server. Takes an IPv4 address with prefix + length, e.g., 192.168.0.1/24. This setting may be useful when the link which + DHCP server running on has multiple static addresses. When unset, one of static addresses in + the link will be automatically selected. Defaults to unset. + + PoolOffset= PoolSize= diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 462907439a..29ab83a425 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -1157,6 +1157,32 @@ int link_request_static_addresses(Link *link) { req->after_configure = static_address_after_configure; } + if (in4_addr_is_set(&link->network->dhcp_server_address)) { + _cleanup_(address_freep) Address *address = NULL; + + r = address_new(&address); + if (r < 0) + return log_oom(); + + 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); + + /* The same address may be explicitly configured in [Address] or [Network] section. + * Configure the DHCP server address only when it is not. */ + if (!link_is_static_address_configured(link, address)) { + Request *req; + + r = link_request_address(link, TAKE_PTR(address), true, &link->static_address_messages, + static_address_handler, &req); + if (r < 0) + return r; + + req->after_configure = static_address_after_configure; + } + } + if (link->static_address_messages == 0) { link->static_addresses_configured = true; link_check_ready(link); diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 07d6eb95d1..0a396a957f 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -29,33 +29,70 @@ 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 && + !in4_addr_is_localhost(&address->in_addr.in) && + in4_addr_is_null(&address->in_addr_peer.in)) { + 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; + } + } +} + +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 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->static_addresses) if (address->family == AF_INET && - in_addr_is_set(address->family, &address->in_addr)) - return address; + !in4_addr_is_localhost(&address->in_addr.in) && + in4_addr_is_null(&address->in_addr_peer.in)) { + *ret = address; + return 0; + } - /* 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; - - return NULL; + return -ENOENT; } static int link_push_uplink_to_dhcp_server( @@ -277,10 +314,9 @@ 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"); - 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, @@ -429,7 +465,6 @@ int config_parse_dhcp_server_relay_agent_suboption( assert(lvalue); assert(rvalue); - if (isempty(rvalue)) { *suboption_value = mfree(*suboption_value); return 0; @@ -492,3 +527,48 @@ int config_parse_dhcp_server_emit( 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; +} diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index e8c8f192f3..d58978cc05 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -2,12 +2,14 @@ #pragma once #include "conf-parser.h" -#include "networkd-link.h" -#include "networkd-util.h" typedef struct Link Link; +typedef struct Network Network; + +void network_adjust_dhcp_server(Network *network); int dhcp4_server_configure(Link *link); 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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b3e03bff3f..6616b14774 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -261,6 +261,7 @@ IPv6AcceptRA.PrefixAllowList, config_parse_ndisc_address_filter, IPv6AcceptRA.PrefixDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_prefix) IPv6AcceptRA.RouteAllowList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_allow_listed_route_prefix) IPv6AcceptRA.RouteDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_route_prefix) +DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id) DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 482c31064f..6f68759a88 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -240,6 +240,8 @@ int network_verify(Network *network) { network_drop_invalid_traffic_control(network); network_drop_invalid_sr_iov(network); + network_adjust_dhcp_server(network); + return 0; } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index e27a27cd02..c1316d2abc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -189,10 +189,11 @@ struct Network { /* DHCP Server Support */ bool dhcp_server; bool dhcp_server_bind_to_interface; + unsigned char dhcp_server_address_prefixlen; + struct in_addr dhcp_server_address; struct in_addr dhcp_server_relay_target; char *dhcp_server_relay_agent_circuit_id; char *dhcp_server_relay_agent_remote_id; - NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; bool dhcp_server_emit_router; bool dhcp_server_emit_timezone; diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 127f26461e..9cbc9e8088 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -364,6 +364,7 @@ BindToInterface= RelayTarget= RelayAgentCircuitId= RelayAgentRemoteId= +ServerAddress= [NextHop] Id= Gateway=