diff options
Diffstat (limited to 'src/network/networkd-ipv4acd.c')
-rw-r--r-- | src/network/networkd-ipv4acd.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c new file mode 100644 index 0000000000..06dfa2d540 --- /dev/null +++ b/src/network/networkd-ipv4acd.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-client.h" +#include "sd-ipv4acd.h" + +#include "networkd-address.h" +#include "networkd-dhcp4.h" +#include "networkd-ipv4acd.h" +#include "networkd-link.h" +#include "networkd-manager.h" + +static int static_address_on_stop(Link *link, Address *address) { + int r; + + assert(link); + assert(address); + + if (address_get(link, address, NULL) < 0) + return 0; + + log_link_debug(link, "Removing address "IPV4_ADDRESS_FMT_STR", as the ACD client is stopped.", + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + + r = address_remove(address, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove address "IPV4_ADDRESS_FMT_STR": %m", + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + + return 0; +} + +static int static_address_on_conflict(Link *link, Address *address) { + int r; + + assert(link); + assert(address); + + if (address_get(link, address, NULL) < 0) { + log_link_warning(link, "Cannot configure requested address "IPV4_ADDRESS_FMT_STR", as an address conflict is detected.", + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + return 0; + } + + log_link_warning(link, "Dropping address "IPV4_ADDRESS_FMT_STR", as an address conflict is detected.", + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + + r = address_remove(address, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove address "IPV4_ADDRESS_FMT_STR": %m", + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + + return 0; +} + +static int dhcp4_address_on_conflict(Link *link) { + int r; + + assert(link); + assert(link->dhcp_client); + + r = sd_dhcp_client_send_decline(link->dhcp_client); + if (r < 0) + log_link_warning_errno(link, r, "Failed to send DHCP DECLINE, ignoring: %m"); + + if (!link->dhcp_lease) + /* Unlikely, but during probing the address, the lease may be lost. */ + return 0; + + log_link_warning(link, "Dropping DHCPv4 lease, as an address conflict is detected."); + r = dhcp4_lease_lost(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to drop DHCPv4 lease: %m"); + + /* It is not necessary to call address_remove() here, as dhcp4_lease_lost() removes it. */ + return 0; +} + +static void on_acd(sd_ipv4acd *acd, int event, void *userdata, bool is_static) { + Address *address = userdata; + Link *link; + int r; + + assert(acd); + assert(address); + assert(address->acd == acd); + assert(address->link); + assert(address->family == AF_INET); + + link = address->link; + + switch (event) { + case SD_IPV4ACD_EVENT_STOP: + if (is_static) { + r = static_address_on_stop(link, address); + if (r < 0) + link_enter_failed(link); + } + + /* We have nothing to do for DHCPv4 lease here, as the dhcp client is already stopped + * when stopping the ipv4acd client. See link_stop_engines(). */ + break; + + case SD_IPV4ACD_EVENT_BIND: + log_link_debug(link, "Successfully claimed address "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); + + address->acd_announced = true; + break; + + case SD_IPV4ACD_EVENT_CONFLICT: + if (is_static) + r = static_address_on_conflict(link, address); + else + r = dhcp4_address_on_conflict(link); + if (r < 0) + link_enter_failed(link); + break; + + default: + assert_not_reached("Invalid IPv4ACD event."); + } +} + +static void static_address_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + on_acd(acd, event, userdata, true); +} + +static void dhcp4_address_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + on_acd(acd, event, userdata, false); +} + +static int ipv4acd_configure(Link *link, const Address *a) { + _cleanup_(address_freep) Address *address = NULL; + int r; + + assert(link); + assert(a); + assert(a->family == AF_INET); + + log_link_debug(link, "Configuring IPv4ACD for address "IPV4_ADDRESS_FMT_STR, + IPV4_ADDRESS_FMT_VAL(a->in_addr.in)); + + r = address_dup(a, &address); + if (r < 0) + return r; + + r = set_ensure_put(&link->addresses_ipv4acd, &address_hash_ops, address); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + address->link = link; + + r = sd_ipv4acd_new(&address->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_attach_event(address->acd, link->manager->event, 0); + if (r < 0) + return r; + + r = sd_ipv4acd_set_ifindex(address->acd, link->ifindex); + if (r < 0) + return r; + + r = sd_ipv4acd_set_mac(address->acd, &link->hw_addr.ether); + if (r < 0) + return r; + + r = sd_ipv4acd_set_address(address->acd, &address->in_addr.in); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(address->acd, + address->is_static ? static_address_on_acd : dhcp4_address_on_acd, + address); + if (r < 0) + return r; + + if (link_has_carrier(link)) { + r = sd_ipv4acd_start(address->acd, true); + if (r < 0) + return r; + } + + TAKE_PTR(address); + return 0; +} + +int ipv4acd_address_is_ready_to_configure(Link *link, const Address *address) { + Address *acd_address; + int r; + + acd_address = set_get(link->addresses_ipv4acd, address); + if (!acd_address) { + r = ipv4acd_configure(link, address); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure IPv4ACD client: %m"); + + return false; + } + + if (!acd_address->acd_announced) + return false; + + r = set_ensure_put(&link->addresses, &address_hash_ops, acd_address); + if (r < 0) + return log_oom(); + if (r == 0) + return log_link_warning_errno(link, SYNTHETIC_ERRNO(EEXIST), "Address already exists."); + + acd_address->flags |= IFA_F_TENTATIVE; + return true; +} + +int ipv4acd_update_mac(Link *link) { + Address *address; + int k, r = 0; + + assert(link); + + if (link->hw_addr.length != ETH_ALEN) + return 0; + if (ether_addr_is_null(&link->hw_addr.ether)) + return 0; + + SET_FOREACH(address, link->addresses_ipv4acd) { + assert(address->acd); + + k = sd_ipv4acd_set_mac(address->acd, &link->hw_addr.ether); + if (k < 0) + r = k; + } + if (r < 0) + link_enter_failed(link); + + return r; +} + +int ipv4acd_start(Link *link) { + Address *address; + int r; + + assert(link); + + SET_FOREACH(address, link->addresses_ipv4acd) { + if (sd_ipv4acd_is_running(address->acd)) + continue; + + r = sd_ipv4acd_start(address->acd, true); + if (r < 0) + return r; + } + + return 0; +} + +int ipv4acd_stop(Link *link) { + Address *address; + int k, r = 0; + + assert(link); + + SET_FOREACH(address, link->addresses_ipv4acd) { + k = sd_ipv4acd_stop(address->acd); + if (k < 0) + r = k; + } + + return r; +} |