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))