device: detect duplicate IPv4 addresses when method=manual
authorBeniamino Galvani <bgalvani@redhat.com>
Wed, 23 Dec 2015 13:15:05 +0000 (14:15 +0100)
committerBeniamino Galvani <bgalvani@redhat.com>
Wed, 20 Jan 2016 10:53:47 +0000 (11:53 +0100)
man/NetworkManager.conf.xml.in
src/devices/nm-device.c

index 31c1cf8..c978712 100644 (file)
@@ -589,13 +589,16 @@ ipv6.ip6-privacy=1
           <term><varname>ethernet.wake-on-lan</varname></term>
         </varlistentry>
         <varlistentry>
-          <term><varname>ipv4.route-metric</varname></term>
+          <term><varname>ipv4.dad-timeout</varname></term>
         </varlistentry>
         <varlistentry>
           <term><varname>ipv4.dhcp-timeout</varname></term>
           <listitem><para>If left unspecified, the default value for
            the interface type is used.</para></listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>ipv4.route-metric</varname></term>
+        </varlistentry>
         <varlistentry>
           <term><varname>ipv6.ip6-privacy</varname></term>
           <listitem><para>If <literal>ipv6.ip6-privacy</literal> is unset, use the content of
index 6e9a156..04b8cf6 100644 (file)
@@ -64,6 +64,7 @@
 #include "nm-lldp-listener.h"
 #include "sd-ipv4ll.h"
 #include "nm-audit-manager.h"
+#include "nm-arping-manager.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
@@ -193,6 +194,14 @@ typedef struct {
        int ifindex;
 } DeleteOnDeactivateData;
 
+typedef void (*ArpingCallback) (NMDevice *, NMIP4Config **, gboolean);
+
+typedef struct {
+       ArpingCallback callback;
+       NMDevice *device;
+       NMIP4Config **configs;
+} ArpingData;
+
 typedef struct _NMDevicePrivate {
        gboolean in_state_changed;
        gboolean initialized;
@@ -300,7 +309,6 @@ typedef struct _NMDevicePrivate {
        NMDhcp4Config * dhcp4_config;
        guint           dhcp4_restart_id;
 
-       guint           arp_round2_id;
        PingInfo        gw_ping;
 
        /* dnsmasq stuff for shared connections */
@@ -315,6 +323,12 @@ typedef struct _NMDevicePrivate {
        sd_ipv4ll *    ipv4ll;
        guint          ipv4ll_timeout;
 
+       /* IPv4 DAD stuff */
+       struct {
+               GSList *          dad_list;
+               NMArpingManager * announcing;
+       } arping;
+
        /* IP6 configuration info */
        NMIP6Config *  ip6_config;
        IpState        ip6_state;
@@ -3660,6 +3674,190 @@ nm_device_check_ip_failed (NMDevice *self, gboolean may_fail)
                                 NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 }
 
+/*********************************************/
+/* IPv4 DAD stuff */
+
+static guint
+get_ipv4_dad_timeout (NMDevice *self)
+{
+       NMConnection *connection;
+       NMSettingIPConfig *s_ip4 = NULL;
+       gs_free char *value = NULL;
+       gint ret = 0;
+
+       connection = nm_device_get_applied_connection (self);
+       if (connection)
+               s_ip4 = nm_connection_get_setting_ip4_config (connection);
+
+       if (s_ip4) {
+               ret = nm_setting_ip_config_get_dad_timeout (s_ip4);
+
+               if (ret < 0) {
+                       value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+                                                                      "ipv4.dad-timeout", self);
+                       ret = _nm_utils_ascii_str_to_int64 (value, 10, -1,
+                                                           NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
+                                                           -1);
+                       ret = ret < 0 ? 0 : ret;
+               }
+       }
+
+       return ret;
+}
+
+static void
+arping_data_destroy (gpointer ptr, GClosure *closure)
+{
+       ArpingData *data = ptr;
+       int i;
+
+       if (data) {
+               for (i = 0; data->configs && data->configs[i]; i++)
+                       g_object_unref (data->configs[i]);
+               g_free (data->configs);
+               g_slice_free (ArpingData, data);
+       }
+}
+
+static void
+ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean success)
+{
+       NMIP4Config *empty;
+
+       if (success) {
+               empty = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+               nm_device_activate_schedule_ip4_config_result (self, empty);
+               g_object_unref (empty);
+       } else {
+               nm_device_queue_state (self, NM_DEVICE_STATE_FAILED,
+                                      NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+       }
+}
+
+static void
+arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *data)
+{
+       NMDevice *self;
+       NMDevicePrivate *priv;
+       const NMPlatformIP4Address *address;
+       gboolean result, success = TRUE;
+       int i, j;
+
+       g_assert (data);
+       self = data->device;
+       priv = NM_DEVICE_GET_PRIVATE (self);
+
+       for (i = 0; data->configs && data->configs[i]; i++) {
+               for (j = 0; j < nm_ip4_config_get_num_addresses (data->configs[i]); j++) {
+                       address = nm_ip4_config_get_address (data->configs[i], j);
+                       result = nm_arping_manager_check_address (arping_manager, address->address);
+                       success &= result;
+
+                       _NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
+                               LOGD_DEVICE,
+                               "IPv4 DAD result: address %s is %s",
+                               nm_utils_inet4_ntop (address->address, NULL),
+                               result ? "unique" : "duplicate");
+               }
+       }
+
+       data->callback (self, data->configs, success);
+
+       priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
+       nm_arping_manager_destroy (arping_manager);
+}
+
+/**
+ * ipv4_dad_start:
+ * @self: device instance
+ * @configs: NULL-terminated array of IPv4 configurations
+ * @cb: callback function
+ *
+ * Start IPv4 DAD on device @self, check addresses in @configs and call @cb
+ * when the procedure ends. @cb will be called in any case, even if DAD can't
+ * be started. @configs will be unreferenced after @cb has been called.
+ */
+static void
+ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
+{
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       NMArpingManager *arping_manager;
+       const NMPlatformIP4Address *address;
+       ArpingData *data;
+       guint timeout;
+       gboolean ret, addr_found;
+       const guint8 *hw_addr;
+       size_t hw_addr_len = 0;
+       GError *error = NULL;
+       guint i, j;
+
+       g_return_if_fail (NM_IS_DEVICE (self));
+       g_return_if_fail (configs);
+       g_return_if_fail (cb);
+
+       for (i = 0, addr_found = FALSE; configs[i]; i++) {
+               if (nm_ip4_config_get_num_addresses (configs[i]) > 0) {
+                       addr_found = TRUE;
+                       break;
+               }
+       }
+
+       timeout = get_ipv4_dad_timeout (self);
+       hw_addr = nm_platform_link_get_address (NM_PLATFORM_GET,
+                                               nm_device_get_ip_ifindex (self),
+                                               &hw_addr_len);
+
+       if (   !timeout
+           || !hw_addr
+           || !hw_addr_len
+           || !addr_found
+           || nm_device_uses_assumed_connection (self)) {
+
+               /* DAD not needed, signal success */
+               cb (self, configs, TRUE);
+
+               for (i = 0; configs[i]; i++)
+                       g_object_unref (configs[i]);
+               g_free (configs);
+
+               return;
+       }
+
+       /* don't take additional references of @arping_manager that outlive @self.
+        * Otherwise, the callback can be invoked on a dangling pointer as we don't
+        * disconnect the handler. */
+       arping_manager = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
+       priv->arping.dad_list = g_slist_append (priv->arping.dad_list, arping_manager);
+
+       data = g_slice_new0 (ArpingData);
+       data->configs = configs;
+       data->callback = cb;
+       data->device = self;
+
+       for (i = 0; configs[i]; i++) {
+               for (j = 0; j < nm_ip4_config_get_num_addresses (configs[i]); j++) {
+                       address = nm_ip4_config_get_address (configs[i], j);
+                       nm_arping_manager_add_address (arping_manager, address->address);
+               }
+       }
+
+       g_signal_connect_data (arping_manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
+                              G_CALLBACK (arping_manager_probe_terminated), data,
+                              arping_data_destroy, 0);
+
+       ret = nm_arping_manager_start_probe (arping_manager, timeout, &error);
+
+       if (!ret) {
+               _LOGW (LOGD_DEVICE, "arping probe failed: %s", error->message);
+
+               /* DAD could not be started, signal success */
+               cb (self, configs, TRUE);
+
+               priv->arping.dad_list = g_slist_remove (priv->arping.dad_list, arping_manager);
+               nm_arping_manager_destroy (arping_manager);
+       }
+}
+
 /*********************************************/
 /* IPv4LL stuff */
 
@@ -4625,10 +4823,17 @@ act_stage3_ip4_config_start (NMDevice *self,
        else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0)
                ret = ipv4ll_start (self, reason);
        else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
-               /* Use only IPv4 config from the connection data */
-               *out_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
-               g_assert (*out_config);
-               ret = NM_ACT_STAGE_RETURN_SUCCESS;
+               NMIP4Config **configs, *config;
+
+               config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+               nm_ip4_config_merge_setting (config,
+                                            nm_connection_get_setting_ip4_config (connection),
+                                            nm_device_get_ip4_route_metric (self));
+
+               configs = g_new0 (NMIP4Config *, 2);
+               configs[0] = config;
+               ipv4_dad_start (self, configs, ipv4_manual_method_apply);
+               ret = NM_ACT_STAGE_RETURN_POSTPONE;
        } else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
                *out_config = shared4_new_config (self, connection, reason);
                if (*out_config) {
@@ -6433,78 +6638,15 @@ start_sharing (NMDevice *self, NMIP4Config *config)
        return TRUE;
 }
 
-static void
-send_arps (NMDevice *self, const char *mode_arg)
-{
-       const char *argv[] = { NULL, mode_arg, "-q", "-I", nm_device_get_ip_iface (self), "-c", "1", NULL, NULL };
-       int ip_arg = G_N_ELEMENTS (argv) - 2;
-       NMConnection *connection;
-       NMSettingIPConfig *s_ip4;
-       int i, num;
-       NMIPAddress *addr;
-       GError *error = NULL;
-
-       connection = nm_device_get_applied_connection (self);
-       if (!connection)
-               return;
-       s_ip4 = nm_connection_get_setting_ip4_config (connection);
-       if (!s_ip4)
-               return;
-       num = nm_setting_ip_config_get_num_addresses (s_ip4);
-       if (num == 0)
-               return;
-
-       argv[0] = nm_utils_find_helper ("arping", NULL, NULL);
-       if (!argv[0]) {
-               _LOGW (LOGD_DEVICE | LOGD_IP4, "arping could not be found; no ARPs will be sent");
-               return;
-       }
-
-       for (i = 0; i < num; i++) {
-               gs_free char *tmp_str = NULL;
-               gboolean success;
-
-               addr = nm_setting_ip_config_get_address (s_ip4, i);
-               argv[ip_arg] = nm_ip_address_get_address (addr);
-
-               _LOGD (LOGD_DEVICE | LOGD_IP4,
-                      "arping: 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 (LOGD_DEVICE | LOGD_IP4,
-                              "arping: could not send ARP for local address %s: %s",
-                              argv[ip_arg], error->message);
-                       g_clear_error (&error);
-               }
-       }
-}
-
-static gboolean
-arp_announce_round2 (gpointer user_data)
-{
-       NMDevice *self = user_data;
-       NMDevicePrivate *priv;
-
-       g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
-
-       priv = NM_DEVICE_GET_PRIVATE (self);
-       priv->arp_round2_id = 0;
-
-       if (   priv->state >= NM_DEVICE_STATE_IP_CONFIG
-           && priv->state <= NM_DEVICE_STATE_ACTIVATED)
-               send_arps (self, "-U");
-
-       return G_SOURCE_REMOVE;
-}
-
 static void
 arp_cleanup (NMDevice *self)
 {
        NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-       nm_clear_g_source (&priv->arp_round2_id);
+       if (priv->arping.announcing) {
+               nm_arping_manager_destroy (priv->arping.announcing);
+               priv->arping.announcing = NULL;
+       }
 }
 
 static void
@@ -6513,10 +6655,19 @@ arp_announce (NMDevice *self)
        NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
        NMConnection *connection;
        NMSettingIPConfig *s_ip4;
-       int num;
+       guint num, i;
+       const guint8 *hw_addr;
+       size_t hw_addr_len = 0;
 
        arp_cleanup (self);
 
+       hw_addr = nm_platform_link_get_address (NM_PLATFORM_GET,
+                                               nm_device_get_ip_ifindex (self),
+                                               &hw_addr_len);
+
+       if (!hw_addr_len || !hw_addr)
+               return;
+
        /* We only care about manually-configured addresses; DHCP- and autoip-configured
         * ones should already have been seen on the network at this point.
         */
@@ -6530,8 +6681,19 @@ arp_announce (NMDevice *self)
        if (num == 0)
                return;
 
-       send_arps (self, "-A");
-       priv->arp_round2_id = g_timeout_add_seconds (2, arp_announce_round2, self);
+       priv->arping.announcing = nm_arping_manager_new (nm_device_get_ip_ifindex (self));
+
+       for (i = 0; i < num; i++) {
+               NMIPAddress *ip = nm_setting_ip_config_get_address (s_ip4, i);
+               in_addr_t addr;
+
+               if (inet_pton (AF_INET, nm_ip_address_get_address (ip), &addr) == 1)
+                       nm_arping_manager_add_address (priv->arping.announcing, addr);
+               else
+                       g_warn_if_reached ();
+       }
+
+       nm_arping_manager_announce_addresses (priv->arping.announcing);
 }
 
 static void
@@ -10595,6 +10757,11 @@ dispose (GObject *object)
 
        _LOGD (LOGD_DEVICE, "disposing");
 
+       g_slist_free_full (priv->arping.dad_list, (GDestroyNotify) nm_arping_manager_destroy);
+       priv->arping.dad_list = NULL;
+
+       arp_cleanup (self);
+
        g_signal_handlers_disconnect_by_func (nm_config_get (), config_changed_update_ignore_carrier, self);
 
        dispatcher_cleanup (self);