summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-ipv4acd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/networkd-ipv4acd.c')
-rw-r--r--src/network/networkd-ipv4acd.c336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c
new file mode 100644
index 0000000..3d5e203
--- /dev/null
+++ b/src/network/networkd-ipv4acd.c
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <net/if.h> /* IFF_LOOPBACK */
+#include <net/if_arp.h> /* ARPHRD_ETHER */
+
+#include "sd-dhcp-client.h"
+#include "sd-ipv4acd.h"
+
+#include "ipvlan.h"
+#include "networkd-address.h"
+#include "networkd-dhcp4.h"
+#include "networkd-ipv4acd.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+ ipv4acd_hash_ops,
+ void, trivial_hash_func, trivial_compare_func,
+ sd_ipv4acd, sd_ipv4acd_unref);
+
+bool link_ipv4acd_supported(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ /* ARPHRD_INFINIBAND seems to potentially support IPv4ACD.
+ * But currently sd-ipv4acd only supports ARPHRD_ETHER. */
+ if (link->iftype != ARPHRD_ETHER)
+ return false;
+
+ if (link->hw_addr.length != ETH_ALEN)
+ return false;
+
+ if (ether_addr_is_null(&link->hw_addr.ether))
+ return false;
+
+ if (streq_ptr(link->kind, "vrf"))
+ return false;
+
+ /* L3 or L3S mode do not support ARP. */
+ if (IN_SET(link_get_ipvlan_mode(link), NETDEV_IPVLAN_MODE_L3, NETDEV_IPVLAN_MODE_L3S))
+ return false;
+
+ return true;
+}
+
+static bool address_ipv4acd_enabled(Link *link, const Address *address) {
+ assert(link);
+ assert(address);
+
+ if (address->family != AF_INET)
+ return false;
+
+ if (!FLAGS_SET(address->duplicate_address_detection, ADDRESS_FAMILY_IPV4))
+ return false;
+
+ /* Currently, only static and DHCP4 addresses are supported. */
+ if (!IN_SET(address->source, NETWORK_CONFIG_SOURCE_STATIC, NETWORK_CONFIG_SOURCE_DHCP4))
+ return false;
+
+ return link_ipv4acd_supported(link);
+}
+
+bool ipv4acd_bound(Link *link, const Address *address) {
+ sd_ipv4acd *acd;
+
+ assert(link);
+ assert(address);
+
+ if (address->family != AF_INET)
+ return true;
+
+ acd = hashmap_get(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in));
+ if (!acd)
+ return true;
+
+ return sd_ipv4acd_is_bound(acd) > 0;
+}
+
+static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_conflict) {
+ int r;
+
+ assert(link);
+ assert(address);
+
+ if (!address_exists(address))
+ return 0; /* Not assigned. */
+
+ if (on_conflict)
+ log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&address->in_addr.in));
+ else
+ log_link_debug(link, "Removing address %s, as the ACD client is stopped.", IN4_ADDR_TO_STRING(&address->in_addr.in));
+
+ r = address_remove(address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to remove address %s: %m", IN4_ADDR_TO_STRING(&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 was 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) {
+ Link *link = ASSERT_PTR(userdata);
+ Address *address = NULL;
+ struct in_addr a;
+ int r;
+
+ assert(acd);
+
+ r = sd_ipv4acd_get_address(acd, &a);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get address from IPv4ACD: %m");
+ link_enter_failed(link);
+ }
+
+ (void) link_get_ipv4_address(link, &a, 0, &address);
+
+ switch (event) {
+ case SD_IPV4ACD_EVENT_STOP:
+ if (!address)
+ break;
+
+ 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 %s", IN4_ADDR_TO_STRING(&a));
+ break;
+
+ case SD_IPV4ACD_EVENT_CONFLICT:
+ if (!address)
+ break;
+
+ log_link_warning(link, "Dropping address %s, as an address conflict was detected.", IN4_ADDR_TO_STRING(&a));
+
+ if (address->source == NETWORK_CONFIG_SOURCE_STATIC)
+ r = static_ipv4acd_address_remove(link, address, /* on_conflict = */ true);
+ else
+ r = dhcp4_address_on_conflict(link);
+ 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 = ASSERT_PTR(userdata);
+ struct hw_addr_data hw_addr;
+
+ assert(mac);
+
+ hw_addr = (struct hw_addr_data) {
+ .length = ETH_ALEN,
+ .ether = *mac,
+ };
+
+ return link_get_by_hw_addr(m, &hw_addr, NULL) >= 0;
+}
+
+static int ipv4acd_start_one(Link *link, sd_ipv4acd *acd) {
+ assert(link);
+ assert(acd);
+
+ if (sd_ipv4acd_is_running(acd))
+ return 0;
+
+ if (!link_has_carrier(link))
+ return 0;
+
+ return sd_ipv4acd_start(acd, /* reset_conflicts = */ true);
+}
+
+int ipv4acd_configure(Link *link, const Address *address) {
+ _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL;
+ sd_ipv4acd *existing;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(address);
+
+ if (address->family != AF_INET)
+ return 0;
+
+ existing = hashmap_get(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in));
+
+ if (!address_ipv4acd_enabled(link, address))
+ return sd_ipv4acd_stop(existing);
+
+ if (existing)
+ return ipv4acd_start_one(link, existing);
+
+ log_link_debug(link, "Configuring IPv4ACD for address %s.", IN4_ADDR_TO_STRING(&address->in_addr.in));
+
+ r = sd_ipv4acd_new(&acd);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_attach_event(acd, link->manager->event, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_ifindex(acd, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_mac(acd, &link->hw_addr.ether);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_address(acd, &address->in_addr.in);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_callback(acd, on_acd, link);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4acd_set_check_mac_callback(acd, ipv4acd_check_mac, link->manager);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_put(&link->ipv4acd_by_address, &ipv4acd_hash_ops, IN4_ADDR_TO_PTR(&address->in_addr.in), acd);
+ if (r < 0)
+ return r;
+
+ return ipv4acd_start_one(link, TAKE_PTR(acd));
+}
+
+void ipv4acd_detach(Link *link, const Address *address) {
+ assert(link);
+ assert(address);
+
+ if (address->family != AF_INET)
+ return;
+
+ sd_ipv4acd_unref(hashmap_remove(link->ipv4acd_by_address, IN4_ADDR_TO_PTR(&address->in_addr.in)));
+}
+
+int ipv4acd_update_mac(Link *link) {
+ sd_ipv4acd *acd;
+ int r;
+
+ assert(link);
+
+ if (link->hw_addr.length != ETH_ALEN)
+ return 0;
+ if (ether_addr_is_null(&link->hw_addr.ether))
+ return 0;
+
+ HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
+ r = sd_ipv4acd_set_mac(acd, &link->hw_addr.ether);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int ipv4acd_start(Link *link) {
+ sd_ipv4acd *acd;
+ int r;
+
+ assert(link);
+
+ HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
+ r = ipv4acd_start_one(link, acd);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int ipv4acd_stop(Link *link) {
+ sd_ipv4acd *acd;
+ int k, r = 0;
+
+ assert(link);
+
+ HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
+ k = sd_ipv4acd_stop(acd);
+ if (k < 0)
+ r = k;
+ }
+
+ return r;
+}
+
+int ipv4acd_set_ifname(Link *link) {
+ sd_ipv4acd *acd;
+ int r;
+
+ assert(link);
+
+ HASHMAP_FOREACH(acd, link->ipv4acd_by_address) {
+ r = sd_ipv4acd_set_ifname(acd, link->ifname);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}