core: match connections to connections instead of devices

This backwards compatible patch adds the possibility to use new
nm_device_generate_connection() API via update_connection() virtual
method implementations in NMDevice subclasses.

Compatibility is achieved by first trying to use the older API and
match_l2_config() virtual method and only then moving on to
update_connection().

The nm_device_generate_connection() calls update_connection() to create
type-specific NMSetting instances and verifies the connection before
returning it. To avoid tinkering with NMSettingConnection in
update_connection() we use a class attribute called connection_type
which is used by nm_device_generate_connection() itself.

Known issues:

* nm_device_generate_connection() method doesn't implement DHCP lease
configuration matching. We shouldn't actually need it but if a use case
for that will come out, we can fix it later.

* nm_device_generate_connection() doesn't fill in the slave-specific
options.

* update_connection() is not implemented and connection_type is not set
in the subclasses. This will be fixed in individual patches.

* NMSetting's compare_property() implementations in combination with
NM_SETTING_COMPARE_FLAG_CANDIDATE are not yet fully ready thus rendering
false negatives in some cases. Same as above.

Acked-by: Dan Winship <danw@gnome.org>
Acked-by: Thomas Haller <thaller@redhat.com>
This commit is contained in:
Pavel Šimerda
2013-06-27 14:39:13 +02:00
parent 2a4a359eb1
commit c1f45eb2df
3 changed files with 217 additions and 19 deletions

View File

@@ -37,6 +37,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <linux/if.h> #include <linux/if.h>
#include "gsystem-local-alloc.h"
#include "nm-glib-compat.h" #include "nm-glib-compat.h"
#include "nm-device.h" #include "nm-device.h"
#include "nm-device-private.h" #include "nm-device-private.h"
@@ -1618,6 +1619,77 @@ can_auto_connect (NMDevice *device,
return nm_device_can_activate (device, connection); return nm_device_can_activate (device, connection);
} }
static gboolean
device_has_config (NMDevice *device)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
/* Check for IP configuration. */
if (priv->ip4_config && nm_ip4_config_get_num_addresses (priv->ip4_config))
return TRUE;
if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
return TRUE;
/* The existence of a software device is good enough. */
if (nm_device_is_software (device))
return TRUE;
return FALSE;
}
NMConnection *
nm_device_generate_connection (NMDevice *device)
{
NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
const char *ifname = nm_device_get_iface (device);
NMConnection *connection;
NMSetting *s_con;
NMSetting *s_ip4;
NMSetting *s_ip6;
gs_free char *uuid = NULL;
gs_free char *name = NULL;
/* If update_connection() is not implemented, just fail. */
if (!klass->update_connection)
return NULL;
/* Return NULL if device is unconfigured. */
if (!device_has_config (device))
return NULL;
nm_log_info (LOGD_DEVICE, "(%s): Generating connection from current device status.", ifname);
connection = nm_connection_new ();
s_con = nm_setting_connection_new ();
s_ip4 = nm_setting_ip4_config_new ();
s_ip6 = nm_setting_ip6_config_new ();
uuid = nm_utils_uuid_generate ();
name = g_strdup_printf ("%s", ifname);
g_object_set (s_con,
NM_SETTING_CONNECTION_UUID, uuid,
NM_SETTING_CONNECTION_ID, name,
NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
NULL);
if (klass->connection_type)
g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type, NULL);
nm_ip4_config_update_setting (priv->ip4_config, (NMSettingIP4Config *) s_ip4);
nm_ip6_config_update_setting (priv->ip6_config, (NMSettingIP6Config *) s_ip6);
nm_connection_add_setting (connection, s_con);
nm_connection_add_setting (connection, s_ip4);
nm_connection_add_setting (connection, s_ip6);
klass->update_connection (device, connection);
/* Check the connection in case of update_connection() bug. */
g_return_val_if_fail (nm_connection_verify (connection, NULL), NULL);
return connection;
}
/** /**
* nm_device_get_best_auto_connection: * nm_device_get_best_auto_connection:
* @dev: an #NMDevice * @dev: an #NMDevice
@@ -1738,12 +1810,29 @@ nm_device_check_connection_compatible (NMDevice *device,
return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection, error); return NM_DEVICE_GET_CLASS (device)->check_connection_compatible (device, connection, error);
} }
/**
* nm_device_can_assume_connections:
* @device: #NMDevice instance
*
* This is a convenience function to determine whether connection assumption
* via old match_l2_config() or new update_connection() virtual functions
* is available for this device.
*
* Use this function when you need to determine whether full cleanup should
* be performed for this device or whether the device should be kept running
* between NetworkManager runs.
*
* Returns: %TRUE for assumable connections and %FALS for full-cleanup connections.
*
* FIXME: Consider turning this method into (a) a device capability or (b) a class
* method.
*/
gboolean gboolean
nm_device_can_assume_connections (NMDevice *device) nm_device_can_assume_connections (NMDevice *device)
{ {
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); NMDeviceClass *klass = NM_DEVICE_GET_CLASS (device);
return !!NM_DEVICE_GET_CLASS (device)->match_l2_config; return klass->match_l2_config || klass->update_connection;
} }
static void static void

View File

@@ -97,6 +97,8 @@ struct _NMDevice {
typedef struct { typedef struct {
GObjectClass parent; GObjectClass parent;
const char *connection_type;
void (*state_changed) (NMDevice *device, void (*state_changed) (NMDevice *device,
NMDeviceState new_state, NMDeviceState new_state,
NMDeviceState old_state, NMDeviceState old_state,
@@ -175,7 +177,24 @@ typedef struct {
gboolean (* spec_match_list) (NMDevice *self, const GSList *specs); 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 update_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 update_connection, while
* subclasses that implement the old API *must* set match_l2_config
* (update_connection is ignored).
*
* Subclasses which don't implement any of the APIs for connection assumption
* *should* leave generate_connection NULL.
*
* The update_connection() virtual function is also used for live
* reconfiguration of the connection according to link level changes.
*/
gboolean (* match_l2_config) (NMDevice *self, NMConnection *connection); gboolean (* match_l2_config) (NMDevice *self, NMConnection *connection);
void (* update_connection) (NMDevice *device, NMConnection *connection);
const GByteArray * (* get_connection_hw_address) (NMDevice *self, const GByteArray * (* get_connection_hw_address) (NMDevice *self,
NMConnection *connection); NMConnection *connection);
@@ -247,6 +266,8 @@ gboolean nm_device_can_activate (NMDevice *dev,
gboolean nm_device_has_carrier (NMDevice *dev); gboolean nm_device_has_carrier (NMDevice *dev);
gboolean nm_device_ignore_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, NMConnection * nm_device_get_best_auto_connection (NMDevice *dev,
GSList *connections, GSList *connections,
char **specific_object); char **specific_object);

View File

@@ -57,6 +57,7 @@
#include "nm-device-tun.h" #include "nm-device-tun.h"
#include "nm-device-macvlan.h" #include "nm-device-macvlan.h"
#include "nm-device-gre.h" #include "nm-device-gre.h"
#include "nm-setting-private.h"
#include "nm-setting-bluetooth.h" #include "nm-setting-bluetooth.h"
#include "nm-setting-connection.h" #include "nm-setting-connection.h"
#include "nm-setting-wireless.h" #include "nm-setting-wireless.h"
@@ -1940,6 +1941,107 @@ 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);
}
/**
* get_connection:
* @manager: #NMManager instance
* @device: #NMDevice instance
* @existing: is set to %TRUE when an existing connection was returned
*
* Returns one of the following:
*
* 1) An existing connection to be assumed.
*
* 2) A generated connection to be assumed.
*
* 3) %NULL when none of the above is available.
*
* Supports both nm-device's match_l2_config() and update_connection().
*/
static NMConnection *
get_connection (NMManager *manager, NMDevice *device, gboolean *existing)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
free_slist GSList *connections = nm_settings_get_connections (priv->settings);
NMConnection *connection = NULL;
GSList *iter;
if (existing)
*existing = FALSE;
/* 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) {
NMConnection *candidate = nm_device_find_assumable_connection (device, connections);
if (candidate) {
nm_log_info (LOGD_DEVICE, "(%s): Found matching connection '%s' (legacy API)",
nm_device_get_iface (device),
nm_connection_get_id (candidate));
if (existing)
*existing = TRUE;
return candidate;
}
}
/* The core of the API is nm_device_generate_connection() function and
* update_connection() virtual method and the convenient connection_type
* class attribute. Subclasses supporting the new API must have
* update_connection() implemented, otherwise nm_device_generate_connection()
* returns NULL.
*/
connection = nm_device_generate_connection (device);
if (!connection) {
nm_log_info (LOGD_DEVICE, "(%s): No existing connection detected.",
nm_device_get_iface (device));
return NULL;
}
/* 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.
*
* When no configured connection matches the generated connection, we keep
* the generated connection instead.
*/
for (iter = connections; iter; iter = iter->next) {
NMConnection *candidate = NM_CONNECTION (iter->data);
if (nm_connection_compare (connection, candidate, NM_SETTING_COMPARE_FLAG_CANDIDATE)) {
nm_log_info (LOGD_DEVICE, "(%s): Found matching connection: '%s'",
nm_device_get_iface (device),
nm_connection_get_id (candidate));
g_object_unref (connection);
if (existing)
*existing = TRUE;
return candidate;
}
}
nm_log_info (LOGD_DEVICE, "(%s): Using generated connection: '%s'",
nm_device_get_iface (device),
nm_connection_get_id (connection));
return connection;
}
static void static void
add_device (NMManager *self, NMDevice *device) add_device (NMManager *self, NMDevice *device)
{ {
@@ -1948,7 +2050,7 @@ add_device (NMManager *self, NMDevice *device)
char *path; char *path;
static guint32 devcount = 0; static guint32 devcount = 0;
const GSList *unmanaged_specs; const GSList *unmanaged_specs;
NMConnection *connection = NULL; NMConnection *connection;
gboolean enabled = FALSE; gboolean enabled = FALSE;
RfKillType rtype; RfKillType rtype;
NMDeviceType devtype; NMDeviceType devtype;
@@ -2034,21 +2136,7 @@ add_device (NMManager *self, NMDevice *device)
nm_log_info (LOGD_CORE, "(%s): exported as %s", iface, path); nm_log_info (LOGD_CORE, "(%s): exported as %s", iface, path);
g_free (path); g_free (path);
/* Check if we should assume the device's active connection by matching its connection = get_connection (self, device, NULL);
* 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));
}
/* Start the device if it's supposed to be managed */ /* Start the device if it's supposed to be managed */
unmanaged_specs = nm_settings_get_unmanaged_specs (priv->settings); unmanaged_specs = nm_settings_get_unmanaged_specs (priv->settings);
@@ -2073,7 +2161,7 @@ add_device (NMManager *self, NMDevice *device)
NMActiveConnection *ac; NMActiveConnection *ac;
GError *error = NULL; 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)); nm_device_get_iface (device));
ac = internal_activate_device (self, device, connection, NULL, FALSE, 0, NULL, TRUE, NULL, &error); ac = internal_activate_device (self, device, connection, NULL, FALSE, 0, NULL, TRUE, NULL, &error);