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.c271
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;
+}