From 78fc92abe8751889bfcc4c7f1a6f4cc846beb611 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 4 Aug 2015 11:30:03 +0200 Subject: [PATCH] device: accept multiple addresses in a DHCPv6 lease When the DHCPv6 lease received from the server contains multiple addresses, dhclient generates a new BOUND event for each of them. Instead of overwriting the previous IP6 configuration for each BOUND event, we should try to detect if the new configuration belongs to the same lease and merge its addresses with the existing one in such case. This allows NetworkManager to configure multiple addresses on an interface via DHCPv6. https://bugzilla.gnome.org/show_bug.cgi?id=681764 https://bugzilla.redhat.com/show_bug.cgi?id=1244293 (cherry picked from commit 1d6e8e8da72412e733b6d45235df227a4d44893f) --- src/devices/nm-device.c | 32 ++++++++++++++++++++++++++---- src/dhcp-manager/nm-dhcp-client.c | 21 ++++++++++++++++---- src/dhcp-manager/nm-dhcp-manager.c | 2 ++ src/nm-iface-helper.c | 1 + 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 09fc143cbb..58895821de 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -332,6 +332,8 @@ typedef struct { NMDhcp6Config * dhcp6_config; /* IP6 config from DHCP */ NMIP6Config * dhcp6_ip6_config; + /* Event ID of the current IP6 config from DHCP */ + char * dhcp6_event_id; guint dhcp6_restart_id; /* allow autoconnect feature */ @@ -3494,6 +3496,7 @@ dhcp4_state_changed (NMDhcpClient *client, NMDhcpState state, NMIP4Config *ip4_config, GHashTable *options, + const char *event_id, gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); @@ -3872,6 +3875,7 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release) priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE; g_clear_object (&priv->dhcp6_ip6_config); + g_clear_pointer (&priv->dhcp6_event_id, g_free); nm_clear_g_source (&priv->dhcp6_restart_id); @@ -4186,10 +4190,12 @@ dhcp6_state_changed (NMDhcpClient *client, NMDhcpState state, NMIP6Config *ip6_config, GHashTable *options, + const char *event_id, gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + guint i; g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE); g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config)); @@ -4198,10 +4204,27 @@ dhcp6_state_changed (NMDhcpClient *client, switch (state) { case NM_DHCP_STATE_BOUND: - g_clear_object (&priv->dhcp6_ip6_config); - if (ip6_config) { - priv->dhcp6_ip6_config = g_object_ref (ip6_config); - dhcp6_update_config (self, priv->dhcp6_config, options); + /* If the server sends multiple IPv6 addresses, we receive a state + * changed event for each of them. Use the event ID to merge IPv6 + * addresses from the same transaction into a single configuration. + */ + if ( ip6_config + && event_id + && priv->dhcp6_event_id + && !strcmp (event_id, priv->dhcp6_event_id)) { + for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) { + nm_ip6_config_add_address (priv->dhcp6_ip6_config, + nm_ip6_config_get_address (ip6_config, i)); + } + } else { + g_clear_object (&priv->dhcp6_ip6_config); + g_clear_pointer (&priv->dhcp6_event_id, g_free); + if (ip6_config) { + priv->dhcp6_ip6_config = g_object_ref (ip6_config); + priv->dhcp6_event_id = g_strdup (event_id); + dhcp6_update_config (self, priv->dhcp6_config, options); + g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG); + } } if (priv->ip6_state == IP_CONF) { @@ -4294,6 +4317,7 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason) g_warn_if_fail (priv->dhcp6_ip6_config == NULL); g_clear_object (&priv->dhcp6_ip6_config); + g_clear_pointer (&priv->dhcp6_event_id, g_free); connection = nm_device_get_connection (self); g_assert (connection); diff --git a/src/dhcp-manager/nm-dhcp-client.c b/src/dhcp-manager/nm-dhcp-client.c index eb31733071..7f5d5a28cd 100644 --- a/src/dhcp-manager/nm-dhcp-client.c +++ b/src/dhcp-manager/nm-dhcp-client.c @@ -36,6 +36,7 @@ #include "nm-dhcp-client.h" #include "nm-dhcp-utils.h" #include "nm-platform.h" +#include "gsystem-local-alloc.h" typedef struct { char * iface; @@ -284,6 +285,7 @@ nm_dhcp_client_set_state (NMDhcpClient *self, GHashTable *options) { NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self); + gs_free char *event_id = NULL; if (new_state >= NM_DHCP_STATE_BOUND) timeout_cleanup (self); @@ -308,19 +310,30 @@ nm_dhcp_client_set_state (NMDhcpClient *self, if ((priv->state == new_state) && (new_state != NM_DHCP_STATE_BOUND)) return; + if (priv->ipv6 && new_state == NM_DHCP_STATE_BOUND) { + char *start, *iaid; + + iaid = g_hash_table_lookup (options, "iaid"); + start = g_hash_table_lookup (options, "life_starts"); + if (iaid && start) + event_id = g_strdup_printf ("%s|%s", iaid, start); + } + nm_log_info (priv->ipv6 ? LOGD_DHCP6 : LOGD_DHCP4, - "(%s): DHCPv%c state changed %s -> %s", + "(%s): DHCPv%c state changed %s -> %s%s%s%s", priv->iface, priv->ipv6 ? '6' : '4', state_to_string (priv->state), - state_to_string (new_state)); + state_to_string (new_state), + NM_PRINT_FMT_QUOTED (event_id, ", event ID=\"", event_id, "\"", "")); priv->state = new_state; g_signal_emit (G_OBJECT (self), signals[SIGNAL_STATE_CHANGED], 0, new_state, ip_config, - options); + options, + event_id); } static gboolean @@ -977,6 +990,6 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (NMDhcpClientClass, state_changed), NULL, NULL, NULL, - G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE); + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE, G_TYPE_STRING); } diff --git a/src/dhcp-manager/nm-dhcp-manager.c b/src/dhcp-manager/nm-dhcp-manager.c index f936f45c58..20ddefc32f 100644 --- a/src/dhcp-manager/nm-dhcp-manager.c +++ b/src/dhcp-manager/nm-dhcp-manager.c @@ -184,6 +184,7 @@ static void client_state_changed (NMDhcpClient *client, NMDhcpState state, GObject *ip_config, GHashTable *options, + const char *event_id, NMDhcpManager *self); static void @@ -204,6 +205,7 @@ client_state_changed (NMDhcpClient *client, NMDhcpState state, GObject *ip_config, GHashTable *options, + const char *event_id, NMDhcpManager *self) { if (state >= NM_DHCP_STATE_TIMEOUT) diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c index 955d36cd7e..18966c5797 100644 --- a/src/nm-iface-helper.c +++ b/src/nm-iface-helper.c @@ -87,6 +87,7 @@ dhcp4_state_changed (NMDhcpClient *client, NMDhcpState state, NMIP4Config *ip4_config, GHashTable *options, + const char *event_id, gpointer user_data) { static NMIP4Config *last_config = NULL;