diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index a7ae34c7c..7e6e05c0a 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -37,6 +37,7 @@ #include #include +#include "gsystem-local-alloc.h" #include "nm-glib-compat.h" #include "nm-device.h" #include "nm-device-private.h" @@ -1618,6 +1619,77 @@ can_auto_connect (NMDevice *device, 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: * @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); } +/** + * 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 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 diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index bad488b7d..2af755006 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -97,6 +97,8 @@ struct _NMDevice { typedef struct { GObjectClass parent; + const char *connection_type; + void (*state_changed) (NMDevice *device, NMDeviceState new_state, NMDeviceState old_state, @@ -175,7 +177,24 @@ 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 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); + void (* update_connection) (NMDevice *device, NMConnection *connection); const GByteArray * (* get_connection_hw_address) (NMDevice *self, NMConnection *connection); @@ -247,6 +266,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 1acd9dd4c..8f1f3799a 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -57,6 +57,7 @@ #include "nm-device-tun.h" #include "nm-device-macvlan.h" #include "nm-device-gre.h" +#include "nm-setting-private.h" #include "nm-setting-bluetooth.h" #include "nm-setting-connection.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 add_device (NMManager *self, NMDevice *device) { @@ -1948,7 +2050,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; @@ -2034,21 +2136,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, NULL); /* Start the device if it's supposed to be managed */ unmanaged_specs = nm_settings_get_unmanaged_specs (priv->settings); @@ -2073,7 +2161,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);