diff options
| author | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2021-02-11 18:11:46 +0100 |
| commit | 80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch) | |
| tree | e3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/devices/nm-acd-manager.c | |
| parent | 65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff) | |
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/devices/nm-acd-manager.c')
| -rw-r--r-- | src/core/devices/nm-acd-manager.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c new file mode 100644 index 00000000..b95f90fd --- /dev/null +++ b/src/core/devices/nm-acd-manager.c @@ -0,0 +1,496 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-acd-manager.h" + +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <linux/if_ether.h> + +#include "platform/nm-platform.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" +#include "n-acd/src/n-acd.h" + +/*****************************************************************************/ + +typedef enum { + STATE_INIT, + STATE_PROBING, + STATE_PROBE_DONE, + STATE_ANNOUNCING, +} State; + +typedef struct { + in_addr_t address; + gboolean duplicate; + NAcdProbe *probe; +} AddressInfo; + +struct _NMAcdManager { + int ifindex; + guint8 hwaddr[ETH_ALEN]; + State state; + GHashTable *addresses; + guint completed; + NAcd * acd; + GSource * event_source; + + NMAcdCallbacks callbacks; + gpointer user_data; +}; + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_IP4 +#define _NMLOG_PREFIX_NAME "acd" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _sbuf[64]; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + self && self->ifindex > 0 \ + ? nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex) \ + : NULL, \ + NULL, \ + "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + self ? nm_sprintf_buf(_sbuf, "[%p,%d]", self, self->ifindex) \ + : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static const char * +_acd_event_to_string(unsigned int event) +{ + switch (event) { + case N_ACD_EVENT_READY: + return "ready"; + case N_ACD_EVENT_USED: + return "used"; + case N_ACD_EVENT_DEFENDED: + return "defended"; + case N_ACD_EVENT_CONFLICT: + return "conflict"; + case N_ACD_EVENT_DOWN: + return "down"; + } + return NULL; +} + +#define ACD_EVENT_TO_STRING_BUF_SIZE 50 + +static const char * +_acd_event_to_string_buf(unsigned event, char buffer[static ACD_EVENT_TO_STRING_BUF_SIZE]) +{ + const char *s; + + s = _acd_event_to_string(event); + if (s) + return s; + + g_snprintf(buffer, ACD_EVENT_TO_STRING_BUF_SIZE, "(%u)", event); + return buffer; +} + +static const char * +acd_error_to_string(int error) +{ + if (error < 0) + return nm_strerror_native(-error); + + switch (error) { + case _N_ACD_E_SUCCESS: + return "success"; + case N_ACD_E_PREEMPTED: + return "preempted"; + case N_ACD_E_INVALID_ARGUMENT: + return "invalid argument"; + } + + g_return_val_if_reached(NULL); +} + +static int +acd_error_to_nmerr(int error, gboolean always_fail) +{ + if (error < 0) + return -nm_errno_native(error); + + if (always_fail) { + if (NM_IN_SET(error, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT)) + return -NME_UNSPEC; + g_return_val_if_reached(-NME_UNSPEC); + } + + /* so, @error is either zero (indicating success) or one + * of the special status codes like N_ACD_E_*. In both cases, + * return the positive value here. */ + if (NM_IN_SET(error, _N_ACD_E_SUCCESS, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT)) + return error; + + g_return_val_if_reached(error); +} + +/*****************************************************************************/ + +/** + * nm_acd_manager_add_address: + * @self: a #NMAcdManager + * @address: an IP address + * + * Add @address to the list of IP addresses to probe. + + * Returns: %TRUE on success, %FALSE if the address was already in the list + */ +gboolean +nm_acd_manager_add_address(NMAcdManager *self, in_addr_t address) +{ + AddressInfo *info; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(self->state == STATE_INIT, FALSE); + + if (g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address))) + return FALSE; + + info = g_slice_new0(AddressInfo); + info->address = address; + + g_hash_table_insert(self->addresses, GUINT_TO_POINTER(address), info); + + return TRUE; +} + +static gboolean +acd_event(int fd, GIOCondition condition, gpointer data) +{ + NMAcdManager *self = data; + NAcdEvent * event; + AddressInfo * info; + gboolean emit_probe_terminated = FALSE; + char address_str[INET_ADDRSTRLEN]; + int r; + + if (n_acd_dispatch(self->acd)) + return G_SOURCE_CONTINUE; + + while (!n_acd_pop_event(self->acd, &event) && event) { + char to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE]; + gs_free char *hwaddr_str = NULL; + gboolean check_probing_done = FALSE; + + switch (event->event) { + case N_ACD_EVENT_READY: + n_acd_probe_get_userdata(event->ready.probe, (void **) &info); + info->duplicate = FALSE; + if (self->state == STATE_ANNOUNCING) { + /* fake probe ended, start announcing */ + r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE); + if (r) { + _LOGW("couldn't announce address %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, address_str), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + } else { + _LOGD("announcing address %s", + _nm_utils_inet4_ntop(info->address, address_str)); + } + } + check_probing_done = TRUE; + break; + case N_ACD_EVENT_USED: + n_acd_probe_get_userdata(event->used.probe, (void **) &info); + info->duplicate = TRUE; + check_probing_done = TRUE; + break; + case N_ACD_EVENT_DEFENDED: + n_acd_probe_get_userdata(event->defended.probe, (void **) &info); + _LOGD("defended address %s from host %s", + _nm_utils_inet4_ntop(info->address, address_str), + (hwaddr_str = + nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender))); + break; + case N_ACD_EVENT_CONFLICT: + n_acd_probe_get_userdata(event->conflict.probe, (void **) &info); + _LOGW("conflict for address %s detected with host %s on interface '%s'", + _nm_utils_inet4_ntop(info->address, address_str), + (hwaddr_str = + nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender)), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex)); + break; + default: + _LOGD("unhandled event '%s'", _acd_event_to_string_buf(event->event, to_string_buffer)); + break; + } + + if (check_probing_done && self->state == STATE_PROBING + && ++self->completed == g_hash_table_size(self->addresses)) { + self->state = STATE_PROBE_DONE; + emit_probe_terminated = TRUE; + } + } + + if (emit_probe_terminated) { + if (self->callbacks.probe_terminated_callback) { + self->callbacks.probe_terminated_callback(self, self->user_data); + } + } + + return G_SOURCE_CONTINUE; +} + +static gboolean +acd_probe_add(NMAcdManager *self, AddressInfo *info, guint64 timeout) +{ + NAcdProbeConfig *probe_config; + int r; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + r = n_acd_probe_config_new(&probe_config); + if (r) { + _LOGW("could not create probe config for %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return FALSE; + } + + n_acd_probe_config_set_ip(probe_config, (struct in_addr){info->address}); + n_acd_probe_config_set_timeout(probe_config, timeout); + + r = n_acd_probe(self->acd, &info->probe, probe_config); + if (r) { + _LOGW("could not start probe for %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + n_acd_probe_config_free(probe_config); + return FALSE; + } + + n_acd_probe_set_userdata(info->probe, info); + n_acd_probe_config_free(probe_config); + + return TRUE; +} + +static int +acd_init(NMAcdManager *self) +{ + NAcdConfig *config; + int r; + + if (self->acd) + return 0; + + r = n_acd_config_new(&config); + if (r) + return r; + + n_acd_config_set_ifindex(config, self->ifindex); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, self->hwaddr, ETH_ALEN); + + r = n_acd_new(&self->acd, config); + n_acd_config_free(config); + return r; +} + +/** + * nm_acd_manager_start_probe: + * @self: a #NMAcdManager + * @timeout: maximum probe duration in milliseconds + * @error: location to store error, or %NULL + * + * Start probing IP addresses for duplicates; when the probe terminates a + * PROBE_TERMINATED signal is emitted. + * + * Returns: 0 on success or a negative NetworkManager error code (NME_*). + */ +int +nm_acd_manager_start_probe(NMAcdManager *self, guint timeout) +{ + GHashTableIter iter; + AddressInfo * info; + gboolean success = FALSE; + int fd, r; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(self->state == STATE_INIT, FALSE); + + r = acd_init(self); + if (r) { + _LOGW("couldn't init ACD for probing on interface '%s': %s", + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return acd_error_to_nmerr(r, TRUE); + } + + self->completed = 0; + + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) + success |= acd_probe_add(self, info, timeout); + + if (success) + self->state = STATE_PROBING; + + nm_assert(!self->event_source); + n_acd_get_fd(self->acd, &fd); + self->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL); + g_source_attach(self->event_source, NULL); + + return success ? 0 : -NME_UNSPEC; +} + +/** + * nm_acd_manager_check_address: + * @self: a #NMAcdManager + * @address: an IP address + * + * Check if an IP address is duplicate. @address must have been added with + * nm_acd_manager_add_address(). + * + * Returns: %TRUE if the address is not duplicate, %FALSE otherwise + */ +gboolean +nm_acd_manager_check_address(NMAcdManager *self, in_addr_t address) +{ + AddressInfo *info; + + g_return_val_if_fail(self, FALSE); + g_return_val_if_fail(NM_IN_SET(self->state, STATE_INIT, STATE_PROBE_DONE), FALSE); + + info = g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address)); + g_return_val_if_fail(info, FALSE); + + return !info->duplicate; +} + +/** + * nm_acd_manager_announce_addresses: + * @self: a #NMAcdManager + * + * Start announcing addresses. + * + * Returns: a negative NetworkManager error number or zero on success. + */ +int +nm_acd_manager_announce_addresses(NMAcdManager *self) +{ + GHashTableIter iter; + AddressInfo * info; + int r; + int fd; + gboolean success = TRUE; + + r = acd_init(self); + if (r) { + _LOGW("couldn't init ACD for announcing addresses on interface '%s': %s", + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + return acd_error_to_nmerr(r, TRUE); + } + + if (self->state == STATE_INIT) { + /* n-acd can't announce without probing, therefore let's + * start a fake probe with zero timeout and then perform + * the announcement. */ + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) { + if (!acd_probe_add(self, info, 0)) + success = FALSE; + } + self->state = STATE_ANNOUNCING; + } else if (self->state == STATE_ANNOUNCING) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_hash_table_iter_init(&iter, self->addresses); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) { + if (info->duplicate) + continue; + r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE); + if (r) { + _LOGW("couldn't announce address %s on interface '%s': %s", + _nm_utils_inet4_ntop(info->address, sbuf), + nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex), + acd_error_to_string(r)); + success = FALSE; + } else + _LOGD("announcing address %s", _nm_utils_inet4_ntop(info->address, sbuf)); + } + } + + if (!self->event_source) { + n_acd_get_fd(self->acd, &fd); + self->event_source = + nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL); + g_source_attach(self->event_source, NULL); + } + + return success ? 0 : -NME_UNSPEC; +} + +static void +destroy_address_info(gpointer data) +{ + AddressInfo *info = (AddressInfo *) data; + + n_acd_probe_free(info->probe); + + g_slice_free(AddressInfo, info); +} + +/*****************************************************************************/ + +NMAcdManager * +nm_acd_manager_new(int ifindex, + const guint8 * hwaddr, + guint hwaddr_len, + const NMAcdCallbacks *callbacks, + gpointer user_data) +{ + NMAcdManager *self; + + g_return_val_if_fail(ifindex > 0, NULL); + g_return_val_if_fail(hwaddr, NULL); + g_return_val_if_fail(hwaddr_len == ETH_ALEN, NULL); + + self = g_slice_new0(NMAcdManager); + + if (callbacks) + self->callbacks = *callbacks; + self->user_data = user_data; + + self->addresses = g_hash_table_new_full(nm_direct_hash, NULL, NULL, destroy_address_info); + self->state = STATE_INIT; + self->ifindex = ifindex; + memcpy(self->hwaddr, hwaddr, ETH_ALEN); + return self; +} + +void +nm_acd_manager_free(NMAcdManager *self) +{ + g_return_if_fail(self); + + if (self->callbacks.user_data_destroy) + self->callbacks.user_data_destroy(self->user_data); + + nm_clear_pointer(&self->addresses, g_hash_table_destroy); + nm_clear_g_source_inst(&self->event_source); + nm_clear_pointer(&self->acd, n_acd_unref); + + g_slice_free(NMAcdManager, self); +} |