commit 1627eef8639205231f00286f7b1b1b4d217c6aeb
Author: Pavel Šimerda <psimerda@redhat.com>
Date:   Thu Jun 27 14:39:13 2013 +0200

    core: match connections to connections instead of devices
    
    This backwards compatible patch adds the possibility to use new
    nm_device_generate_connection() API via generate_connection() virtual
    method implementations in NMDevice subclasses.
    
    Compatibility is achieved by first trying to use the former API and
    match_l2_config() virtual method and only then moving on to
    generate_connection().
    
    The nm_device_generate_connection() wrapper calls nm_connection_verify()
    to avoid using the result of NMDevice's generate_connection() directly
    with a pleasant side effects of enforcing validity for generated
    connections.
    
    For more details see the source code comments.
    
    Please note that the generate_connection() method doesn't implement DHCP
    lease configuration matching. We mostly agreed on IRC that lease
    matching is not necessary. Even if there is a use case for that, we're
    going to fix it later.

diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 79e7f28..24a5929 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -37,6 +37,7 @@
 #include <fcntl.h>
 #include <linux/if.h>
 
+#include "gsystem-local-alloc.h"
 #include "nm-glib-compat.h"
 #include "nm-device.h"
 #include "nm-device-private.h"
@@ -1584,6 +1585,88 @@ can_auto_connect (NMDevice *device,
 }
 
 /**
+ * nm_device_generate_connection:
+ * @device: an #NMDevice
+ *
+ * Returns: a new connection constructed from the current device state, or
+ * %NULL if the current device state doesn't suggest a running connection.
+ */
+NMConnection *
+nm_device_generate_connection (NMDevice *device)
+{
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
+	NMConnection *connection;
+
+	/* A NMDevice subclass can legally reset generate_connection() virtual
+	 * method reference. In that case, nm_device_generate_connection() must
+	 * return NULL to indicate that connection generation is not supported.
+	 */
+	if (!klass->generate_connection)
+		return NULL;
+
+	connection = klass->generate_connection (device);
+
+	/* This API is implicitly opt-in, as the NMDevice implementation of
+	 * generate_connection doesn't set the 'connection.type' and therefore
+	 * doesn't produce valid connections. To avoid potential issues, we
+	 * verify the connection now.
+	 */
+	if (!nm_connection_verify (connection, NULL)) {
+		g_object_unref (connection);
+		return NULL;
+	}
+
+	return connection;
+}
+
+static NMConnection *
+generate_connection (NMDevice *device)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMConnection *connection = NULL;
+	gboolean generate = FALSE;
+
+	/* Check for IP configuration */
+	if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
+		generate = TRUE;
+	if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
+		generate = TRUE;
+
+	/* Check for a software device */
+	if (nm_platform_link_is_software (nm_device_get_ifindex (device)))
+		generate = TRUE;
+
+	if (!generate)
+		return NULL;
+
+	{
+		NMSetting *s_con = nm_setting_connection_new ();
+		NMSetting *s_ip4 = nm_setting_ip4_config_new ();
+		NMSetting *s_ip6 = nm_setting_ip4_config_new ();
+		gs_free char *uuid = nm_utils_uuid_generate ();
+		gs_free char *name = g_strdup_printf ("%s-foreign", nm_device_get_iface (device));
+
+		nm_ip4_config_update_setting (priv->ip4_config, (NMSettingIP4Config *) s_ip4);
+		nm_ip6_config_update_setting (priv->ip6_config, (NMSettingIP6Config *) s_ip6);
+
+		g_object_set (s_con,
+					  NM_SETTING_CONNECTION_UUID, uuid,
+					  NM_SETTING_CONNECTION_ID, name,
+					  NM_SETTING_CONNECTION_TYPE, NM_SETTING_GENERIC_SETTING_NAME,
+					  NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+					  NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
+					  NULL);
+
+		connection = nm_connection_new ();
+		nm_connection_add_setting (connection, s_con);
+		nm_connection_add_setting (connection, s_ip4);
+		nm_connection_add_setting (connection, s_ip6);
+
+		return connection;
+	}
+}
+
+/**
  * nm_device_get_best_auto_connection:
  * @dev: an #NMDevice
  * @connections: (element-type #NMConnection): a list of connections
@@ -5135,6 +5218,7 @@ nm_device_class_init (NMDeviceClass *klass)
 
 	klass->spec_match_list = spec_match_list;
 	klass->can_auto_connect = can_auto_connect;
+	klass->generate_connection = generate_connection;
 	klass->check_connection_compatible = check_connection_compatible;
 	klass->check_connection_available = check_connection_available;
 	klass->is_up = is_up;
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 4ce5927..bba44b4 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -172,7 +172,22 @@ typedef struct {
 
 	gboolean        (* spec_match_list)     (NMDevice *self, const GSList *specs);
 
+	/* FIXME: We currently support match_l2_config() virtual function for
+	 * compatibility. When match_l2_config() is not present, we use the
+	 * new generate_connection() virtual function which should first call
+	 * NMDevice's implementation and then perform type-specific adjustments.
+	 * 
+	 * Therefore subclasses that implement the new API *must* leave
+	 * match_l2_config set to NULL and implement generate_connection, while
+	 * subclasses that implement the old API *must* set match_l2_config
+	 * (generate_connection is ignored).
+	 *
+	 * Subclasses which don't implement any of the APIs for connection assumption
+	 * *should* explicitly set generate_connection to NULL but they aren't
+	 * required to do so. See comments inside nm_device_generate_connection().
+	 */
 	gboolean        (* match_l2_config) (NMDevice *self, NMConnection *connection);
+	NMConnection *  (* generate_connection) (NMDevice *device);
 
 	const GByteArray * (* get_connection_hw_address) (NMDevice *self,
 	                                                  NMConnection *connection);
@@ -242,6 +257,8 @@ gboolean		nm_device_can_activate   (NMDevice     *dev,
 gboolean        nm_device_has_carrier    (NMDevice *dev);
 gboolean		nm_device_ignore_carrier (NMDevice *dev);
 
+NMConnection * nm_device_generate_connection (NMDevice *device);
+
 NMConnection * nm_device_get_best_auto_connection (NMDevice *dev,
                                                    GSList *connections,
                                                    char **specific_object);
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 3ee798f..314faae 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -1885,6 +1885,80 @@ device_auth_request_cb (NMDevice *device,
 	}
 }
 
+/* This should really be moved to gsystem. */
+#define free_slist __attribute__ ((cleanup(local_slist_free)))
+static void
+local_slist_free (void *loc)
+{
+	GSList **location = loc;
+
+	if (location)
+		g_slist_free (*location);
+}
+
+static gboolean
+connection_matches_candidate (NMConnection *connection, NMConnection *candidate)
+{
+	/* TODO: For now we simply use nm_connection_compare with the FUZZY flag
+	 * but proper matching will require a more fine-tuned implementation.
+	 */
+	return nm_connection_compare (candidate, connection, NM_SETTING_COMPARE_FLAG_FUZZY);
+}
+
+static NMConnection *
+get_connection (NMManager *manager, NMDevice *device)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	free_slist GSList *connections = nm_settings_get_connections (priv->settings);
+	NMConnection *connection = NULL;
+	GSList *iter;
+
+	/* We still support the older API to match a NMDevice object to an
+	 * existing connection using nm_device_find_assumable_connection().
+	 *
+	 * When the older API is still available for a particular device
+	 * type, we use it. To opt for the newer interface, the NMDevice
+	 * subclass must omit the match_l2_config virtual function
+	 * implementation.
+	 */
+	if (NM_DEVICE_GET_CLASS (device)->match_l2_config) {
+		connection = nm_device_find_assumable_connection (device, connections);
+		if (connection)
+			return connection;
+	}
+
+	/* The core of the API is nm_device_generate_connection() virtual function
+	 * that is overridden in each NMDevice subclass that supports connection
+	 * generation.
+	 */
+	connection = nm_device_generate_connection (device);
+
+	/* Now we need to compare the generated connection to each configured
+	 * connection. The comparison function is the heart of the connection
+	 * assumption implementation and it must compare the connections very
+	 * carefully to sort out various corner cases. Also, the comparison is
+	 * not entirely symmetric.
+	 */
+	if (connection) {
+		for (iter = connections; iter; iter = iter->next) {
+			NMConnection *candidate = NM_CONNECTION (iter->data);
+
+			if (connection_matches_candidate (connection, candidate)) {
+				nm_log_dbg (LOGD_DEVICE, "(%s): found matching connection '%s'",
+							nm_device_get_iface (device),
+							nm_connection_get_id (connection));
+				g_object_unref (connection);
+				connection = candidate;
+			}
+		}
+	}
+
+	/* When no configured connection matches the generated connection, we keep
+	 * the generated connection instead.
+	 */
+	return connection;
+}
+
 static void
 add_device (NMManager *self, NMDevice *device)
 {
@@ -1893,7 +1967,7 @@ add_device (NMManager *self, NMDevice *device)
 	char *path;
 	static guint32 devcount = 0;
 	const GSList *unmanaged_specs;
-	NMConnection *connection = NULL;
+	NMConnection *connection;
 	gboolean enabled = FALSE;
 	RfKillType rtype;
 	NMDeviceType devtype;
@@ -1973,21 +2047,7 @@ add_device (NMManager *self, NMDevice *device)
 	nm_log_info (LOGD_CORE, "(%s): exported as %s", iface, path);
 	g_free (path);
 
-	/* Check if we should assume the device's active connection by matching its
-	 * config with an existing system connection.
-	 */
-	if (nm_device_can_assume_connections (device)) {
-		GSList *connections = NULL;
-
-		connections = nm_settings_get_connections (priv->settings);
-		connection = nm_device_find_assumable_connection (device, connections);
-		g_slist_free (connections);
-
-		if (connection)
-			nm_log_dbg (LOGD_DEVICE, "(%s): found existing device connection '%s'",
-			            nm_device_get_iface (device),
-			            nm_connection_get_id (connection));
-	}
+	connection = get_connection (self, device);
 
 	/* Start the device if it's supposed to be managed */
 	unmanaged_specs = nm_settings_get_unmanaged_specs (priv->settings);
@@ -2012,7 +2072,7 @@ add_device (NMManager *self, NMDevice *device)
 		NMActiveConnection *ac;
 		GError *error = NULL;
 
-		nm_log_dbg (LOGD_DEVICE, "(%s): will attempt to assume existing connection",
+		nm_log_dbg (LOGD_DEVICE, "(%s): will attempt to assume connection",
 		            nm_device_get_iface (device));
 
 		ac = internal_activate_device (self, device, connection, NULL, FALSE, 0, NULL, TRUE, NULL, &error);
