device: detect duplicate IPv4 addresses when method=manual

This commit is contained in:
Beniamino Galvani 2015-12-23 14:15:05 +01:00
parent 83b712aa42
commit 28f6e8b4d2
2 changed files with 246 additions and 76 deletions

View 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

View 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);