blob: 009cde27de19679bfc3b3fe420f4eb18dbd0290e [file] [log] [blame] [raw]
/* 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;
}