| /* 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_ipv4acd_address_remove(Link *link, Address *address, bool on_conflict) { |
| int r; |
| |
| assert(link); |
| assert(address); |
| |
| /* Prevent form the address being freed. */ |
| address_enter_probing(address); |
| |
| if (!address_exists(address)) |
| return 0; /* Not assigned. */ |
| |
| if (on_conflict) |
| log_link_warning(link, "Dropping address "IPV4_ADDRESS_FMT_STR", as an address conflict was detected.", |
| IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); |
| else |
| 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); |
| 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, Address *address) { |
| 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 was detected."); |
| r = dhcp4_lease_lost(link); |
| if (r < 0) |
| return log_link_warning_errno(link, r, "Failed to drop DHCPv4 lease: %m"); |
| |
| /* make the address will be freed. */ |
| address_cancel_probing(address); |
| |
| /* 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) { |
| Address *address = userdata; |
| Link *link; |
| int r; |
| |
| assert(acd); |
| assert(address); |
| assert(address->acd == acd); |
| assert(address->link); |
| assert(address->family == AF_INET); |
| assert(IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4)); |
| |
| link = address->link; |
| |
| switch (event) { |
| case SD_IPV4ACD_EVENT_STOP: |
| if (address->source == NETWORK_CONFIG_SOURCE_STATIC) { |
| r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ false); |
| 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_cancel_probing(address); |
| break; |
| |
| case SD_IPV4ACD_EVENT_CONFLICT: |
| log_link_warning(link, "Dropping address "IPV4_ADDRESS_FMT_STR", as an address conflict was detected.", |
| IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); |
| |
| if (address->source == NETWORK_CONFIG_SOURCE_STATIC) |
| r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ true); |
| else |
| r = dhcp4_address_on_conflict(link, address); |
| if (r < 0) |
| link_enter_failed(link); |
| break; |
| |
| default: |
| assert_not_reached(); |
| } |
| } |
| |
| static int ipv4acd_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) { |
| Manager *m = userdata; |
| struct hw_addr_data hw_addr; |
| |
| assert(m); |
| assert(mac); |
| |
| hw_addr = (struct hw_addr_data) { |
| .length = ETH_ALEN, |
| .ether = *mac, |
| }; |
| |
| return link_get_by_hw_addr(m, &hw_addr, NULL) >= 0; |
| } |
| |
| int ipv4acd_configure(Address *address) { |
| Link *link; |
| int r; |
| |
| assert(address); |
| |
| link = ASSERT_PTR(address->link); |
| |
| if (address->family != AF_INET) |
| return 0; |
| |
| if (!FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV4)) |
| return 0; |
| |
| if (link->hw_addr.length != ETH_ALEN || hw_addr_is_null(&link->hw_addr)) |
| return 0; |
| |
| /* Currently, only static and DHCP4 addresses are supported. */ |
| assert(IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4)); |
| |
| if (address->acd) { |
| address_enter_probing(address); |
| return 0; |
| } |
| |
| log_link_debug(link, "Configuring IPv4ACD for address "IPV4_ADDRESS_FMT_STR, |
| IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); |
| |
| 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, on_acd, address); |
| if (r < 0) |
| return r; |
| |
| r = sd_ipv4acd_set_check_mac_callback(address->acd, ipv4acd_check_mac, link->manager); |
| if (r < 0) |
| return r; |
| |
| if (link_has_carrier(link)) { |
| r = sd_ipv4acd_start(address->acd, true); |
| if (r < 0) |
| return r; |
| } |
| |
| address_enter_probing(address); |
| return 0; |
| } |
| |
| 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) { |
| if (!address->acd) |
| continue; |
| |
| 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) { |
| if (!address->acd) |
| continue; |
| |
| 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) { |
| if (!address->acd) |
| continue; |
| |
| k = sd_ipv4acd_stop(address->acd); |
| if (k < 0) |
| r = k; |
| } |
| |
| return r; |
| } |
| |
| int ipv4acd_set_ifname(Link *link) { |
| Address *address; |
| int r; |
| |
| assert(link); |
| |
| SET_FOREACH(address, link->addresses) { |
| if (!address->acd) |
| continue; |
| |
| r = sd_ipv4acd_set_ifname(address->acd, link->ifname); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |