Note, previously we replaced the previous option with the same option code with
new one. But, DHCP message can have multiple options with same option code.
Hence, this make the conf parser not replace, but append new one.
There are four DHCP options with confusing names:
Option 43: Vendor-Specific Information
Option 60: Vendor Class Identifier
Option 124: Vendor-Identifying Vendor Class
Option 125: Vendor-Identifying Vendor-Specific Information
Let's use their full names for their corresponding enums.
The helper would create a new ref, even though we had one handy
and didn't need to create a new ref. So change the helper to
take an existing reference.
We have a number of *_unref_and_replace macros. One could think that
they are like the various free_and_replace variants, but they actually
create a new ref to the passed object. The free_and_replace variants
take ownership of the argument. This inconsistency is surprising. Rename
all those functions to have "_new_ref" at the end to make the difference
clear.
I am really not a fan of full code lines passed to macros as parameters.
Let's get rid of the 3rd parameter of FOREACH_OPTION() hence:
1. Let's return errors just as a regular value (though a negative one),
that can be handled via a OPTION_ERROR case statement for the switch.
This normalizes handling of the error, just like any other event
returned by the option parser.
2. In order to avoid exploding the amount of boilerplate in each use
(that just propagates the error on OPTION_ERROR), let's then
introduce an explicit FOREACH_OPTION_OR_RETURN(), that returns from
the calling function on its own (and makes that clear in the name).
Together this cleans up, normalizes the logic and shortens the code.
It was requested to make the 'c', 'opt', and 'arg' params the same, i.e.
defined through the FOREACH_OPTION macro. But we can't do that easily,
because 'c' was defined in the for loop definition, and we can only
define variables of the same type in that way. Also, in some cases we
need only 'c', in other cases with need 'c' and 'arg, in some cases 'c'
and 'opt', and in other cases all three. We'd need to either
conditionalize or mark those variables with _unused_ to deal with
compiler warnings. But a different approach works quite nicely: add
state.opt and state.arg to show the current option and it's argument.
(The short names are picked on purpose to reduce verbosity since those
are used a lot.)
This is mostly equivalent to dhcp_packet_verify_headers(), but
- it optionally returns the UDP payload as iovec, and
- supports IP header with options,
- check packet length more strictly.
Then make dhcp_packet_append_ip_headers() just a wrapper of the new
function. Currently, the wrapper is inefficient, but will be removed in
a later commit.
discover_attempts should be reset only when
- the client is stopped, to make the counter starts from zero on
the next invocation.
- we acquire a bound lease, to make the counter starts from zero
when the lease is expired.
request_attempts should be reset only when the client enter a new state
that sends DHCPREQUEST, that is, when enter one of the REBOOTING,
REQUESTING, RENEWING, and REBINDING state.
This moves resetting counter to client_set_state() as it should happen
only when the state transition.
Otherwise, if we emit the notification without a valid bound lease, networkd
may be confused (of course should not, but for safety).
Also, increment the delay before calling client_start_delayed().
Otherwise, the first reboot is done instantaneously.
If we fail to setup timer event sources about the lease lifetime or
T1/T2, then the lease will be never updated, and the user (networkd)
will not receive any notification about the expire. The situation is
terrible. Let's stop the client with error code earlier, and notify the
failure to networkd.
This drop delay after ACK, as it has many problems. See comment in
sd_dhcp_client_is_waiting_for_ipv6_connectivity() for more details.
This way, the logic becomes much much simpler.
Also, do not restart the client if we lost IPv6 connectivity in
sd_dhcp_client side, but restart the client by networkd. As,
sd_dhcp_client does not know if we can start the client or not,
e.g., the interface may be currently down.
It is not necessary to enable another timer event source to send
DHCPREQUEST from the T1/T2 timer event source. Just call the callback
function for sending message.
Also, T1 hits only we have a bound lease. Drop spurious conditions.
When we start the client, any previous state/configuration should be cleaned.
Let's effectively do the same thing as client_initialize() in that
function.
This also several assertions in client_start_delayed() to
sd_dhcp_client_start(). These kind of checks should be done earlier.
Similarly, enter the REBOOTING state before sending DHCPREQUEST on reboot.
Also, this makes DHCPREQUEST message is sent several times also in REBOOTING
state. Previously, we wait about 4 seconds after DHCPREQUEST on reboot, and
entered the init state if no response. Now we wait 1 second after the first
DHCPREQUEST, resend another DHCPREQUEST, wait 2 seconds, then enter the
init state if no response. So, even in the worst case, we have slight
speed up.
To make gracefully ignore unexpected messages from outside at unexpected timing.
This potentially reduces work load to handle such messages, and slightly
reduces attack surface by malicious DHCP messages.
This also makes the socket fd is owned by the relevant IO event source.
Except for the performance optimization and security hardening, this
should not change any behaviors. So, just refactoring.