diff options
author | Beniamino Galvani <bgalvani@redhat.com> | 2015-11-19 11:44:03 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2016-01-12 14:35:35 +0100 |
commit | d918f6184c9b1135cac355cf4fd43e779531b6dd (patch) | |
tree | 2dfd7614976897b983f9c633c7c6715b471d00bc | |
parent | 88bc909266a22d780cd705419635805a40e096d2 (diff) | |
download | NetworkManager-d918f6184c9b1135cac355cf4fd43e779531b6dd.tar.gz |
core: add NMArpingManager
Add a new object which implements the logic for announcing IP
addresses and detecting duplicates using arping.
Based-on-patch-by: Jiří Klimeš <jklimes@redhat.com>
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/devices/nm-arping-manager.c | 453 | ||||
-rw-r--r-- | src/devices/nm-arping-manager.h | 56 | ||||
-rw-r--r-- | src/nm-types.h | 1 |
4 files changed, 512 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 7e85831035..d07fed6e2e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -240,6 +240,8 @@ libNetworkManager_la_SOURCES = \ devices/nm-device.h \ devices/nm-lldp-listener.c \ devices/nm-lldp-listener.h \ + devices/nm-arping-manager.c \ + devices/nm-arping-manager.h \ devices/nm-device-ethernet-utils.c \ devices/nm-device-ethernet-utils.h \ devices/nm-device-factory.c \ diff --git a/src/devices/nm-arping-manager.c b/src/devices/nm-arping-manager.c new file mode 100644 index 0000000000..a139c2cd44 --- /dev/null +++ b/src/devices/nm-arping-manager.c @@ -0,0 +1,453 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "config.h" + +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "nm-default.h" +#include "nm-arping-manager.h" +#include "nm-platform.h" +#include "nm-utils.h" +#include "NetworkManagerUtils.h" + +typedef enum { + STATE_INIT, + STATE_PROBING, + STATE_PROBE_DONE, + STATE_ANNOUNCING, +} State; + +typedef struct { + int ifindex; + State state; + GHashTable *addresses; + guint completed; + guint timer; + guint round2_id; + void *user_data; + GDestroyNotify user_data_destroy_func; +} NMArpingManagerPrivate; + +typedef struct { + in_addr_t address; + GPid pid; + guint watch; + gboolean duplicate; + NMArpingManager *manager; +} AddressInfo; + +enum { + PROBE_TERMINATED, + LAST_SIGNAL, +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (NMArpingManager, nm_arping_manager, G_TYPE_OBJECT) + +#define NM_ARPING_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_ARPING_MANAGER, NMArpingManagerPrivate)) + +#define _NMLOG_DOMAIN LOGD_IP4 +#define _NMLOG_PREFIX_NAME "arping" +#define _NMLOG(level, ...) \ + G_STMT_START { \ + char _sbuf[64]; \ + \ + nm_log ((level), _NMLOG_DOMAIN, \ + "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + self ? nm_sprintf_buf (_sbuf, "[%p,%d]", \ + self, \ + NM_ARPING_MANAGER_GET_PRIVATE (self)->ifindex) : "" \ + _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ + } G_STMT_END + +/** + * nm_arping_manager_add_address: + * @self: a #NMArpingManager + * @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_arping_manager_add_address (NMArpingManager *self, in_addr_t address) +{ + NMArpingManagerPrivate *priv; + AddressInfo *info; + + g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE); + priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + g_return_val_if_fail (priv->state == STATE_INIT, FALSE); + + if (g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address))) { + _LOGD ("address already exists"); + return FALSE; + } + + info = g_new0 (AddressInfo, 1); + info->address = address; + info->manager = g_object_ref (self); + + g_hash_table_insert (priv->addresses, GUINT_TO_POINTER (address), info); + + return TRUE; +} + +static void +arping_notify_probe_terminated (NMArpingManager *self) +{ + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + g_signal_emit (self, signals[PROBE_TERMINATED], 0, priv->user_data); + if (priv->user_data_destroy_func) + g_clear_pointer (&priv->user_data, priv->user_data_destroy_func); +} + +static void +arping_watch_cb (GPid pid, gint status, gpointer user_data) +{ + AddressInfo *info = user_data; + NMArpingManager *self = info->manager; + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + const char *addr; + + info->pid = 0; + info->watch = 0; + addr = nm_utils_inet4_ntop (info->address, NULL); + + if (WIFEXITED (status)) { + if (WEXITSTATUS (status) != 0) { + _LOGD ("%s already used in the %s network", + addr, nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex)); + info->duplicate = TRUE; + } else + _LOGD ("DAD succeeded for %s", addr); + } else { + _LOGD ("stopped unexpectedly with status %d for %s", status, addr); + } + + if (++priv->completed == g_hash_table_size (priv->addresses)) { + priv->state = STATE_PROBE_DONE; + nm_clear_g_source (&priv->timer); + arping_notify_probe_terminated (self); + } +} + +static gboolean +arping_timeout_cb (gpointer user_data) +{ + NMArpingManager *self = user_data; + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + GHashTableIter iter; + AddressInfo *info; + + priv->timer = 0; + + g_hash_table_iter_init (&iter, priv->addresses); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) { + nm_clear_g_source (&info->watch); + if (info->pid) { + _LOGD ("DAD timed out for %s", + nm_utils_inet4_ntop (info->address, NULL)); + nm_utils_kill_child_async (info->pid, SIGTERM, LOGD_IP4, + "arping", 1000, NULL, NULL); + info->pid = 0; + } + } + + priv->state = STATE_PROBE_DONE; + arping_notify_probe_terminated (self); + + return G_SOURCE_REMOVE; +} + +/** + * nm_arping_manager_start_probe: + * @self: a #NMArpingManager + * @timeout: maximum probe duration in milliseconds + * @user_data: pointer to user data to be passed back to signal handler + * @user_data_destroy_func: function to destroy @user_data + * @error: location to store error, or %NULL + * + * Start probing IP addresses for duplicates; when the probe terminates a + * PROBE_TERMINATED signal is emitted with @user_data as argument, then + * @user_data is released using @user_data_destroy. When @self is destroyed or + * reset, @user_data_destroy_func will be called on @user_data if the signal was + * not emitted. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +nm_arping_manager_start_probe (NMArpingManager *self, guint timeout, void *user_data, + GDestroyNotify user_data_destroy_func, GError **error) +{ + const char *argv[] = { NULL, "-D", "-q", "-I", NULL, "-c", NULL, "-w", NULL, NULL, NULL }; + NMArpingManagerPrivate *priv; + GHashTableIter iter; + AddressInfo *info; + gs_free char *timeout_str = NULL; + + g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (timeout, FALSE); + + priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + g_return_val_if_fail (priv->state == STATE_INIT, FALSE); + + argv[4] = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex); + g_return_val_if_fail (argv[4], FALSE); + + priv->completed = 0; + priv->user_data = user_data; + priv->user_data_destroy_func = user_data_destroy_func; + + argv[0] = nm_utils_find_helper ("arping", NULL, NULL); + if (!argv[0]) { + g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, + "arping could not be found"); + return FALSE; + } + + timeout_str = g_strdup_printf ("%u", timeout / 1000 + 2); + argv[6] = timeout_str; + argv[8] = timeout_str; + + g_hash_table_iter_init (&iter, priv->addresses); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) { + gs_free char *tmp_str = NULL; + gboolean success; + + argv[9] = nm_utils_inet4_ntop (info->address, NULL); + _LOGD ("run %s", (tmp_str = g_strjoinv (" ", (char **) argv))); + + success = g_spawn_async (NULL, (char **) argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &info->pid, NULL); + + info->watch = g_child_watch_add (info->pid, arping_watch_cb, info); + } + + priv->timer = g_timeout_add (timeout, arping_timeout_cb, self); + priv->state = STATE_PROBING; + + return TRUE; +} + +/** + * nm_arping_manager_reset: + * @self: a #NMArpingManager + * + * Stop any operation in progress and reset @self to the initial state. + */ +void +nm_arping_manager_reset (NMArpingManager *self) +{ + NMArpingManagerPrivate *priv; + + g_return_if_fail (NM_IS_ARPING_MANAGER (self)); + priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + nm_clear_g_source (&priv->timer); + nm_clear_g_source (&priv->round2_id); + g_hash_table_remove_all (priv->addresses); + + if (priv->user_data_destroy_func && priv->user_data) + priv->user_data_destroy_func (priv->user_data); + + priv->user_data_destroy_func = NULL; + priv->user_data = NULL; + priv->state = STATE_INIT; +} + +/** + * nm_arping_manager_check_address: + * @self: a #NMArpingManager + * @address: an IP address + * + * Check if an IP address is duplicate. @address must have been added with + * nm_arping_manager_add_address(). + * + * Returns: %TRUE if the address is not duplicate, %FALSE otherwise + */ +gboolean +nm_arping_manager_check_address (NMArpingManager *self, in_addr_t address) +{ + NMArpingManagerPrivate *priv; + AddressInfo *info; + + g_return_val_if_fail (NM_IS_ARPING_MANAGER (self), FALSE); + priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + g_return_val_if_fail ( priv->state == STATE_INIT + || priv->state == STATE_PROBE_DONE, FALSE); + + info = g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address)); + g_return_val_if_fail (info, FALSE); + + return !info->duplicate; +} + +static void +send_announcements (NMArpingManager *self, const char *mode_arg) +{ + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + const char *argv[] = { NULL, mode_arg, "-q", "-I", NULL, "-c", "1", NULL, NULL }; + int ip_arg = G_N_ELEMENTS (argv) - 2; + GError *error = NULL; + GHashTableIter iter; + AddressInfo *info; + + argv[4] = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex); + g_return_if_fail (argv[4]); + + argv[0] = nm_utils_find_helper ("arping", NULL, NULL); + if (!argv[0]) { + _LOGW ("arping could not be found; no ARPs will be sent"); + return; + } + + g_hash_table_iter_init (&iter, priv->addresses); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) { + gs_free char *tmp_str = NULL; + gboolean success; + + if (info->duplicate) + continue; + + argv[ip_arg] = nm_utils_inet4_ntop (info->address, NULL); + _LOGD ("run %s", (tmp_str = g_strjoinv (" ", (char **) argv))); + + success = g_spawn_async (NULL, (char **) argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, NULL, &error); + if (!success) { + _LOGW ("could not send ARP for address %s: %s", argv[ip_arg], + error->message); + g_clear_error (&error); + } + } +} + +static gboolean +arp_announce_round2 (gpointer self) +{ + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + priv->round2_id = 0; + send_announcements (self, "-U"); + priv->state = STATE_INIT; + g_hash_table_remove_all (priv->addresses); + + return G_SOURCE_REMOVE; +} + +/** + * nm_arping_manager_announce_addresses: + * @self: a #NMArpingManager + * + * Start announcing addresses. + */ +void +nm_arping_manager_announce_addresses (NMArpingManager *self) +{ + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + g_return_if_fail ( priv->state == STATE_INIT + || priv->state == STATE_PROBE_DONE); + + send_announcements (self, "-A"); + nm_clear_g_source (&priv->round2_id); + priv->round2_id = g_timeout_add_seconds (2, arp_announce_round2, self); + priv->state = STATE_ANNOUNCING; +} + +static void +destroy_address_info (gpointer data) +{ + AddressInfo *info = (AddressInfo *) data; + + nm_clear_g_source (&info->watch); + + if (info->pid) { + nm_utils_kill_child_async (info->pid, SIGTERM, LOGD_IP4, "arping", + 1000, NULL, NULL); + } + + g_object_unref (info->manager); + g_free (info); +} + +static void +dispose (GObject *object) +{ + NMArpingManager *self = NM_ARPING_MANAGER (object); + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + nm_clear_g_source (&priv->timer); + nm_clear_g_source (&priv->round2_id); + g_clear_pointer (&priv->addresses, g_hash_table_destroy); + + G_OBJECT_CLASS (nm_arping_manager_parent_class)->dispose (object); +} + +static void +nm_arping_manager_init (NMArpingManager *self) +{ + NMArpingManagerPrivate *priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + + priv->addresses = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, destroy_address_info); + priv->state = STATE_INIT; +} + +NMArpingManager * +nm_arping_manager_new (int ifindex) +{ + NMArpingManager *self; + NMArpingManagerPrivate *priv; + + self = g_object_new (NM_TYPE_ARPING_MANAGER, NULL); + priv = NM_ARPING_MANAGER_GET_PRIVATE (self); + priv->ifindex = ifindex; + + return self; +} + +static void +nm_arping_manager_class_init (NMArpingManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (NMArpingManagerPrivate)); + + object_class->dispose = dispose; + + signals[PROBE_TERMINATED] = + g_signal_new (NM_ARPING_MANAGER_PROBE_TERMINATED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); +} diff --git a/src/devices/nm-arping-manager.h b/src/devices/nm-arping-manager.h new file mode 100644 index 0000000000..86b4458ebe --- /dev/null +++ b/src/devices/nm-arping-manager.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_ARPING_MANAGER__ +#define __NM_ARPING_MANAGER__ + +#include <netinet/in.h> + +#include "nm-glib.h" +#include "nm-types.h" + +G_BEGIN_DECLS + +#define NM_TYPE_ARPING_MANAGER (nm_arping_manager_get_type ()) +#define NM_ARPING_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ARPING_MANAGER, NMArpingManager)) +#define NM_ARPING_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_ARPING_MANAGER, NMArpingManagerClass)) +#define NM_IS_ARPING_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_ARPING_MANAGER)) +#define NM_IS_ARPING_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_ARPING_MANAGER)) +#define NM_ARPING_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_ARPING_MANAGER, NMArpingManagerClass)) + +#define NM_ARPING_MANAGER_PROBE_TERMINATED "probe-terminated" + +struct _NMArpingManager { + GObject parent; +}; + +typedef struct { + GObjectClass parent; +} NMArpingManagerClass; + +GType nm_arping_manager_get_type (void); + +NMArpingManager *nm_arping_manager_new (int ifindex); +gboolean nm_arping_manager_add_address (NMArpingManager *self, in_addr_t address); +gboolean nm_arping_manager_start_probe (NMArpingManager *self, guint timeout, void *user_data, + GDestroyNotify user_data_destroy_func, GError **error); +gboolean nm_arping_manager_check_address (NMArpingManager *self, in_addr_t address); +void nm_arping_manager_announce_addresses (NMArpingManager *self); +void nm_arping_manager_reset (NMArpingManager *self); + +G_END_DECLS + +#endif /* __NM_ARPING_MANAGER__ */ diff --git a/src/nm-types.h b/src/nm-types.h index 7934fcbbed..0fda9216a7 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -35,6 +35,7 @@ typedef struct _NMAuthSubject NMAuthSubject; typedef struct _NMBusManager NMBusManager; typedef struct _NMConfig NMConfig; typedef struct _NMConfigData NMConfigData; +typedef struct _NMArpingManager NMArpingManager; typedef struct _NMConnectionProvider NMConnectionProvider; typedef struct _NMConnectivity NMConnectivity; typedef struct _NMDefaultRouteManager NMDefaultRouteManager; |