n-dhcp4: support init-reboot state

Currently the client always starts from the INIT state (i.e. sending a
discover message). If a requested-ip was specified by the caller, it
is added as an option in the discover.

It was reported that some DHCP servers don't respond to discover
messages with the requested-ip option set [1][2].

The RFC allows to skip the discover by entering the INIT-REBOOT state
and starting directly with a broadcast request message containing the
requested IP address. Implement that.

[1] https://bugzilla.redhat.com/show_bug.cgi?id=1781856
[2] https://gitlab.freedesktop.org/NetworkManager/NetworkManager/issues/310

(cherry picked from commit af03b77980)
This commit is contained in:
Beniamino Galvani 2019-12-18 14:51:14 +01:00
parent 0a428153f8
commit 965219a4cd
3 changed files with 80 additions and 7 deletions

View file

@ -319,7 +319,6 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
switch (connection->request->userdata.type) {
case N_DHCP4_C_MESSAGE_DISCOVER:
case N_DHCP4_C_MESSAGE_SELECT:
case N_DHCP4_C_MESSAGE_REBOOT:
case N_DHCP4_C_MESSAGE_INFORM:
/*
* Resend with an exponential backoff and a one second random
@ -338,6 +337,7 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
break;
case N_DHCP4_C_MESSAGE_REBIND:
case N_DHCP4_C_MESSAGE_RENEW:
case N_DHCP4_C_MESSAGE_REBOOT:
/*
* Resend every sixty seconds with a one second random slack.
*

View file

@ -170,8 +170,6 @@ _c_public_ void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeCon
* INIT-REBOOT path, as described by the DHCP specification. In most cases, you
* do not want this.
*
* XXX: This is currently not implemented, and setting the property has no effect.
*
* Background: The INIT-REBOOT path allows a DHCP client to skip
* server-discovery when rebooting/resuming their machine. The DHCP
* client simply re-requests the lease it had acquired before. This
@ -438,11 +436,17 @@ int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
if (r)
return r;
if (probe->config->init_reboot && probe->config->requested_ip.s_addr != INADDR_ANY)
probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT;
else
probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT;
if (active) {
/*
* Defer the sending of DISCOVER by a random amount (by default up to 9 seconds).
*/
probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL));
if (probe->state == N_DHCP4_CLIENT_PROBE_STATE_INIT)
probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL));
probe->client->current_probe = probe;
} else {
r = n_dhcp4_client_probe_raise(probe,
@ -575,6 +579,14 @@ void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeou
n_dhcp4_c_connection_get_timeout(&probe->connection, &timeout);
switch (probe->state) {
case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
/* send DHCP request immediately */
timeout = 1;
break;
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
if (probe->ns_reinit && (!timeout || probe->ns_reinit < timeout))
timeout = probe->ns_reinit;
break;
case N_DHCP4_CLIENT_PROBE_STATE_INIT:
if (probe->ns_deferred && (!timeout || probe->ns_deferred < timeout))
timeout = probe->ns_deferred;
@ -626,6 +638,52 @@ static int n_dhcp4_client_probe_outgoing_append_options(NDhcp4ClientProbe *probe
return 0;
}
static int n_dhcp4_client_probe_transition_reboot(NDhcp4ClientProbe *probe, uint64_t ns_now) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
int r;
switch (probe->state) {
case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
r = n_dhcp4_c_connection_listen(&probe->connection);
if (r)
return r;
r = n_dhcp4_c_connection_reboot_new(&probe->connection, &request, &probe->config->requested_ip);
if (r)
return r;
r = n_dhcp4_client_probe_outgoing_append_options(probe, request);
if (r)
return r;
r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
if (r)
return r;
else
request = NULL; /* consumed */
probe->state = N_DHCP4_CLIENT_PROBE_STATE_REBOOTING;
probe->ns_reinit = ns_now + 2000000000ULL;
break;
case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
case N_DHCP4_CLIENT_PROBE_STATE_INIT:
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
default:
abort();
break;
}
return 0;
}
static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, uint64_t ns_now) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
int r;
@ -635,12 +693,14 @@ static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, ui
r = n_dhcp4_c_connection_listen(&probe->connection);
if (r)
return r;
/* fall-through */
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
r = n_dhcp4_c_connection_discover_new(&probe->connection, &request);
if (r)
return r;
if (probe->config->requested_ip.s_addr != INADDR_ANY) {
if (!probe->config->init_reboot && probe->config->requested_ip.s_addr != INADDR_ANY) {
r = n_dhcp4_outgoing_append_requested_ip(request, probe->config->requested_ip);
if (r)
return r;
@ -663,7 +723,6 @@ static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, ui
case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
@ -876,6 +935,7 @@ static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4I
break;
case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
r = n_dhcp4_client_probe_raise(probe,
&node,
@ -900,7 +960,6 @@ static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4I
case N_DHCP4_CLIENT_PROBE_STATE_INIT:
case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
@ -1079,6 +1138,19 @@ int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_no
int r;
switch (probe->state) {
case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
r = n_dhcp4_client_probe_transition_reboot(probe, ns_now);
if (r)
return r;
break;
case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
if (ns_now >= probe->ns_reinit) {
r = n_dhcp4_client_probe_transition_deferred(probe, ns_now);
if (r)
return r;
}
break;
case N_DHCP4_CLIENT_PROBE_STATE_INIT:
if (ns_now >= probe->ns_deferred) {
r = n_dhcp4_client_probe_transition_deferred(probe, ns_now);

View file

@ -351,6 +351,7 @@ struct NDhcp4ClientProbe {
unsigned int state; /* current probe state */
uint64_t ns_deferred; /* timeout for deferred action */
uint64_t ns_reinit;
NDhcp4ClientLease *current_lease; /* current lease */
NDhcp4CConnection connection; /* client connection wrapper */