diff --git a/man/systemd.network.xml b/man/systemd.network.xml index b86bcf03ef..7353b57419 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1396,6 +1396,16 @@ + + MaxAttempts= + + Specifies how many times the DHCPv4 client configuration should be attempted. Takes a + number or infinity. Defaults to infinity. + Note that the time between retries is increased exponentially, so the network will not be + overloaded even if this number is high. + + + DUIDType= diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index f10ae3ed3b..33a8ef7999 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -32,8 +32,6 @@ #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) -#define MAX_CLIENT_ATTEMPT 64 - #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) @@ -90,7 +88,8 @@ struct sd_dhcp_client { uint32_t mtu; uint32_t xid; usec_t start_time; - unsigned attempt; + uint64_t attempt; + uint64_t max_attempts; usec_t request_sent; sd_event_source *timeout_t1; sd_event_source *timeout_t2; @@ -522,6 +521,14 @@ int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { return 0; } +int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) { + assert_return(client, -EINVAL); + + client->max_attempts = max_attempts; + + return 0; +} + int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { assert_return(client, -EINVAL); @@ -553,7 +560,7 @@ static int client_initialize(sd_dhcp_client *client) { (void) event_source_disable(client->timeout_t2); (void) event_source_disable(client->timeout_expire); - client->attempt = 1; + client->attempt = 0; client->state = DHCP_STATE_INIT; client->xid = 0; @@ -1052,12 +1059,12 @@ static int client_timeout_resend( case DHCP_STATE_REQUESTING: case DHCP_STATE_BOUND: - if (client->attempt < MAX_CLIENT_ATTEMPT) - client->attempt *= 2; + if (client->attempt < client->max_attempts) + client->attempt++; else goto error; - next_timeout = time_now + (client->attempt - 1) * USEC_PER_SEC; + next_timeout = time_now + ((UINT64_C(1) << MIN(client->attempt, (uint64_t) 6)) - 1) * USEC_PER_SEC; break; @@ -1081,17 +1088,15 @@ static int client_timeout_resend( r = client_send_discover(client); if (r >= 0) { client->state = DHCP_STATE_SELECTING; - client->attempt = 1; - } else { - if (client->attempt >= MAX_CLIENT_ATTEMPT) - goto error; - } + client->attempt = 0; + } else if (client->attempt >= client->max_attempts) + goto error; break; case DHCP_STATE_SELECTING: r = client_send_discover(client); - if (r < 0 && client->attempt >= MAX_CLIENT_ATTEMPT) + if (r < 0 && client->attempt >= client->max_attempts) goto error; break; @@ -1101,7 +1106,7 @@ static int client_timeout_resend( case DHCP_STATE_RENEWING: case DHCP_STATE_REBINDING: r = client_send_request(client); - if (r < 0 && client->attempt >= MAX_CLIENT_ATTEMPT) + if (r < 0 && client->attempt >= client->max_attempts) goto error; if (client->state == DHCP_STATE_INIT_REBOOT) @@ -1253,7 +1258,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) client->fd = asynchronous_close(client->fd); client->state = DHCP_STATE_REBINDING; - client->attempt = 1; + client->attempt = 0; r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, client->mac_addr, @@ -1273,7 +1278,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) DHCP_CLIENT_DONT_DESTROY(client); client->state = DHCP_STATE_RENEWING; - client->attempt = 1; + client->attempt = 0; return client_initialize_time_events(client); } @@ -1557,7 +1562,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i if (r >= 0) { client->state = DHCP_STATE_REQUESTING; - client->attempt = 1; + client->attempt = 0; r = event_reset_time(client->event, &client->timeout_resend, clock_boottime_or_monotonic(), @@ -1592,7 +1597,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i notify_event = r; client->state = DHCP_STATE_BOUND; - client->attempt = 1; + client->attempt = 0; client->last_addr = client->lease->address; @@ -1931,10 +1936,10 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .state = DHCP_STATE_INIT, .ifindex = -1, .fd = -1, - .attempt = 1, .mtu = DHCP_DEFAULT_MIN_SIZE, .port = DHCP_PORT_CLIENT, .anonymize = !!anonymize, + .max_attempts = (uint64_t) -1, }; /* NOTE: this could be moved to a function. */ if (anonymize) { diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index c630359027..01f4e955c8 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -533,7 +533,7 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) { r = sd_ipv4ll_start(link->ipv4ll); if (r < 0) { - log_link_warning(link, "Could not acquire IPv4 link-local address: %m"); + log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); return; } } @@ -833,5 +833,11 @@ int dhcp4_configure(Link *link) { return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set listen port: %m"); } + if (link->network->dhcp_max_attempts > 0) { + r = sd_dhcp_client_set_max_attempts(link->dhcp_client, link->network->dhcp_max_attempts); + if (r < 0) + return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set max attempts: %m"); + } + return dhcp4_set_client_identifier(link); } diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index fb4bf266a6..79e90f2f5a 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -175,7 +175,7 @@ static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata) { r = sd_ipv4ll_restart(ll); if (r < 0) - log_link_warning(link, "Could not acquire IPv4 link-local address"); + log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); break; case SD_IPV4LL_EVENT_BIND: r = ipv4ll_address_claimed(ll, link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 8232b069a8..646cd91e93 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -140,6 +140,7 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) +DHCP.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class) DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index a85d5ede28..15c34b078d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -1575,6 +1575,54 @@ int config_parse_section_route_table( return 0; } +int config_parse_dhcp_max_attempts( + 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 = data; + uint64_t a; + int r; + + assert(network); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + network->dhcp_max_attempts = 0; + return 0; + } + + if (streq(rvalue, "infinity")) { + network->dhcp_max_attempts = (uint64_t) -1; + return 0; + } + + r = safe_atou64(rvalue, &a); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse DHCP maximum attempts, ignoring: %s", rvalue); + return 0; + } + + if (a == 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "%s= must be positive integer or 'infinity', ignoring: %s", lvalue, rvalue); + return 0; + } + + network->dhcp_max_attempts = a; + + return 0; +} + DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, "Failed to parse DHCP use domains setting"); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 852144da3c..052e8d5cc0 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -116,6 +116,7 @@ struct Network { char *dhcp_vendor_class_identifier; char **dhcp_user_class; char *dhcp_hostname; + uint64_t dhcp_max_attempts; unsigned dhcp_route_metric; uint32_t dhcp_route_table; uint16_t dhcp_client_port; @@ -315,6 +316,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class); CONFIG_PARSER_PROTOTYPE(config_parse_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts); /* Legacy IPv4LL support */ CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index bd0d429df6..d9b57e2c73 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -154,6 +154,9 @@ int sd_dhcp_client_get_client_id( int sd_dhcp_client_set_mtu( sd_dhcp_client *client, uint32_t mtu); +int sd_dhcp_client_set_max_attempts( + sd_dhcp_client *client, + uint64_t attempt); int sd_dhcp_client_set_client_port( sd_dhcp_client *client, uint16_t port); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index fd3256e784..8149a35d1e 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -199,6 +199,7 @@ MaxLeaseTimeSec= DefaultLeaseTimeSec= EmitTimezone= DNS= +MaxAttempts= [DHCPv4] UseHostname= UseMTU= diff --git a/test/test-network/conf/dhcp-client-with-ipv4ll-fallback.network b/test/test-network/conf/dhcp-client-with-ipv4ll-fallback-with-dhcp-server.network similarity index 100% rename from test/test-network/conf/dhcp-client-with-ipv4ll-fallback.network rename to test/test-network/conf/dhcp-client-with-ipv4ll-fallback-with-dhcp-server.network diff --git a/test/test-network/conf/dhcp-client-with-ipv4ll-fallback-without-dhcp-server.network b/test/test-network/conf/dhcp-client-with-ipv4ll-fallback-without-dhcp-server.network new file mode 100644 index 0000000000..7faff5f9e5 --- /dev/null +++ b/test/test-network/conf/dhcp-client-with-ipv4ll-fallback-without-dhcp-server.network @@ -0,0 +1,10 @@ +[Match] +Name=veth99 + +[Network] +DHCP=ipv4 +LinkLocalAddressing=fallback +IPv6AcceptRA=no + +[DHCP] +MaxAttempts=1 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 184ab2d03a..4301f8c5dc 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -1880,7 +1880,8 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): 'dhcp-client-route-metric.network', 'dhcp-client-route-table.network', 'dhcp-client-vrf.network', - 'dhcp-client-with-ipv4ll-fallback.network', + 'dhcp-client-with-ipv4ll-fallback-with-dhcp-server.network', + 'dhcp-client-with-ipv4ll-fallback-without-dhcp-server.network', 'dhcp-client.network', 'dhcp-server-veth-peer.network', 'dhcp-v4-server-veth-peer.network', @@ -2177,10 +2178,11 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, 'onlink') - def test_dhcp_client_with_ipv4ll_fallback(self): + def test_dhcp_client_with_ipv4ll_fallback_with_dhcp_server(self): self.copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', - 'dhcp-client-with-ipv4ll-fallback.network') + 'dhcp-client-with-ipv4ll-fallback-with-dhcp-server.network') self.start_networkd(0) + self.wait_online(['veth-peer:carrier']) self.start_dnsmasq(lease_time='2m') self.wait_online(['veth99:routable', 'veth-peer:routable']) @@ -2213,6 +2215,24 @@ class NetworkdNetworkDHCPClientTests(unittest.TestCase, Utilities): self.search_words_in_dnsmasq_log('DHCPOFFER', show_all=True) + def test_dhcp_client_with_ipv4ll_fallback_without_dhcp_server(self): + self.copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', + 'dhcp-client-with-ipv4ll-fallback-without-dhcp-server.network') + self.start_networkd(0) + self.wait_online(['veth99:degraded', 'veth-peer:routable']) + + output = subprocess.check_output(['ip', 'address', 'show', 'dev', 'veth99']).rstrip().decode('utf-8') + print(output) + + output = subprocess.check_output(['ip', '-6', 'address', 'show', 'dev', 'veth99', 'scope', 'global', 'dynamic']).rstrip().decode('utf-8') + self.assertNotRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global dynamic') + output = subprocess.check_output(['ip', '-6', 'address', 'show', 'dev', 'veth99', 'scope', 'link']).rstrip().decode('utf-8') + self.assertRegex(output, 'inet6 .* scope link') + output = subprocess.check_output(['ip', '-4', 'address', 'show', 'dev', 'veth99', 'scope', 'global', 'dynamic']).rstrip().decode('utf-8') + self.assertNotRegex(output, 'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic veth99') + output = subprocess.check_output(['ip', '-4', 'address', 'show', 'dev', 'veth99', 'scope', 'link']).rstrip().decode('utf-8') + self.assertRegex(output, 'inet .* scope link') + if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=3))