From 808136ac02db02737fdc4ebae48b03d67a54fce9 Mon Sep 17 00:00:00 2001 From: "Nathanael D. Noblet" Date: Wed, 1 Aug 2012 11:16:48 -0600 Subject: [PATCH] core: add AvailableConnections property for NMDevice objects Implements a new property that provides a list of currently available connections a device could connect to. For example if a connection for a particular wireless connection exists and that wireless network appears in the scan list it would show in the AvailableConnections property of the device. (dcbw: found a slightly cleaner way to do this; it's a lot like the check_connection_compatible class method, except it deals with live network data too. So convert the subclass methods to just check additional live network data, and have the base device class handle adding the connection to the hash and all the associated signalling. Also fix a bug where the available connections were not updated when a device moved from UNAVAILABLE to available, its available connections were not updated) --- introspection/nm-device.xml | 5 + src/nm-device-private.h | 2 + src/nm-device-wifi.c | 59 ++++++++--- src/nm-device.c | 197 ++++++++++++++++++++++++++++++++++++ src/nm-device.h | 12 +++ src/wimax/nm-device-wimax.c | 22 ++++ 6 files changed, 281 insertions(+), 16 deletions(-) diff --git a/introspection/nm-device.xml b/introspection/nm-device.xml index 20bbb9170f..5b2c98b3c6 100644 --- a/introspection/nm-device.xml +++ b/introspection/nm-device.xml @@ -120,6 +120,11 @@ The general type of the network device; ie Ethernet, WiFi, etc. + + + An array of object paths of every configured connection that is currently 'available' through this device. + + diff --git a/src/nm-device-private.h b/src/nm-device-private.h index 824beb5a7a..7740fc6d09 100644 --- a/src/nm-device-private.h +++ b/src/nm-device-private.h @@ -69,4 +69,6 @@ gboolean nm_device_match_ip_config (NMDevice *device, NMConnection *connection); NMConnectionProvider *nm_device_get_connection_provider (NMDevice *device); +void nm_device_recheck_available_connections (NMDevice *device); + #endif /* NM_DEVICE_PRIVATE_H */ diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index e2ff77e4dd..60512136cb 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -884,9 +884,13 @@ _set_hw_addr (NMDeviceWifi *self, const guint8 *addr, const char *detail) } static void -access_point_removed (NMDeviceWifi *device, NMAccessPoint *ap) +remove_access_point (NMDeviceWifi *device, NMAccessPoint *ap) { + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(device); g_signal_emit (device, signals[ACCESS_POINT_REMOVED], 0, ap); + priv->ap_list = g_slist_remove (priv->ap_list, ap); + g_object_unref(ap); + nm_device_recheck_available_connections (NM_DEVICE (device)); } static void @@ -897,10 +901,7 @@ remove_all_aps (NMDeviceWifi *self) /* Remove outdated APs */ while (g_slist_length (priv->ap_list)) { NMAccessPoint *ap = NM_AP (priv->ap_list->data); - - access_point_removed (self, ap); - priv->ap_list = g_slist_remove (priv->ap_list, ap); - g_object_unref (ap); + remove_access_point (self, ap); } g_slist_free (priv->ap_list); priv->ap_list = NULL; @@ -952,9 +953,7 @@ real_deactivate (NMDevice *dev) * and thus the AP culling never happens. (bgo #569241) */ if (orig_ap && nm_ap_get_fake (orig_ap)) { - access_point_removed (self, orig_ap); - priv->ap_list = g_slist_remove (priv->ap_list, orig_ap); - g_object_unref (orig_ap); + remove_access_point (self, orig_ap); } /* Reset MAC address back to initial address */ @@ -1069,6 +1068,33 @@ real_check_connection_compatible (NMDevice *device, return TRUE; } + +static gboolean +real_check_connection_available (NMDevice *device, NMConnection *connection) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + NMSettingWireless *s_wifi; + const char *mode; + GSList *ap_iter = NULL; + + s_wifi = nm_connection_get_setting_wireless (connection); + + /* Ad-Hoc connections are always available because they may be started + * at any time. + */ + mode = nm_setting_wireless_get_mode (s_wifi); + if (g_strcmp0 (mode, "adhoc") == 0) + return TRUE; + + /* check if its visible */ + for (ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) { + if (nm_ap_check_compatible (NM_AP (ap_iter->data), connection)) + return TRUE; + } + + return FALSE; +} + /* * List of manufacturer default SSIDs that are often unchanged by users. * @@ -1902,6 +1928,7 @@ merge_scanned_ap (NMDeviceWifi *self, priv->ap_list = g_slist_prepend (priv->ap_list, merge_ap); nm_ap_export_to_dbus (merge_ap); g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, merge_ap); + nm_device_recheck_available_connections (NM_DEVICE (self)); } } @@ -1964,9 +1991,7 @@ cull_scan_list (NMDeviceWifi *self) ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", ssid ? "'" : ""); - access_point_removed (self, outdated_ap); - priv->ap_list = g_slist_remove (priv->ap_list, outdated_ap); - g_object_unref (outdated_ap); + remove_access_point (self, outdated_ap); removed++; } g_slist_free (outdated_list); @@ -1977,6 +2002,9 @@ cull_scan_list (NMDeviceWifi *self) ap_list_dump (self); + if(removed > 0) + nm_device_recheck_available_connections (NM_DEVICE (self)); + return FALSE; } @@ -2839,6 +2867,7 @@ real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) priv->ap_list = g_slist_prepend (priv->ap_list, ap); nm_ap_export_to_dbus (ap); g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, ap); + nm_device_recheck_available_connections (NM_DEVICE (self)); } nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap)); @@ -3162,7 +3191,6 @@ static void activation_failure_handler (NMDevice *dev) { NMDeviceWifi *self = NM_DEVICE_WIFI (dev); - NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); NMAccessPoint *ap; NMConnection *connection; @@ -3180,9 +3208,7 @@ activation_failure_handler (NMDevice *dev) * list because we don't have any scan or capability info * for it, and they are pretty much useless. */ - access_point_removed (self, ap); - priv->ap_list = g_slist_remove (priv->ap_list, ap); - g_object_unref (ap); + remove_access_point (self, ap); } } } @@ -3544,6 +3570,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass) parent_class->get_best_auto_connection = real_get_best_auto_connection; parent_class->is_available = real_is_available; parent_class->check_connection_compatible = real_check_connection_compatible; + parent_class->check_connection_available = real_check_connection_available; parent_class->complete_connection = real_complete_connection; parent_class->set_enabled = real_set_enabled; @@ -3633,7 +3660,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass) g_signal_new ("access-point-removed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_removed), + 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, diff --git a/src/nm-device.c b/src/nm-device.c index 9432204dbc..bb9ac3dd27 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -65,6 +65,7 @@ #include "nm-connection-provider.h" #include "nm-posix-signals.h" #include "nm-manager-auth.h" +#include "nm-dbus-glib-types.h" static void impl_device_disconnect (NMDevice *device, DBusGMethodInvocation *context); @@ -123,6 +124,7 @@ enum { PROP_TYPE_DESC, PROP_RFKILL_TYPE, PROP_IFINDEX, + PROP_AVAILABLE_CONNECTIONS, LAST_PROP }; @@ -169,6 +171,7 @@ typedef struct { gboolean managed; /* whether managed by NM or not */ RfKillType rfkill_type; gboolean firmware_missing; + GHashTable * available_connections; guint32 ip4_address; @@ -241,6 +244,12 @@ typedef struct { NMDevice * master; NMConnectionProvider *con_provider; + + /* connection provider signals for available connections property */ + guint cp_added_id; + guint cp_loaded_id; + guint cp_removed_id; + guint cp_updated_id; } NMDevicePrivate; static void nm_device_take_down (NMDevice *dev, gboolean wait, NMDeviceStateReason reason); @@ -258,10 +267,19 @@ static gboolean nm_device_set_ip6_config (NMDevice *dev, static gboolean nm_device_activate_ip6_config_commit (gpointer user_data); +static gboolean real_check_connection_available (NMDevice *device, NMConnection *connection); + +static void _clear_available_connections (NMDevice *device, gboolean do_signal); + static void dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release); static const char *reason_to_string (NMDeviceStateReason reason); +static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); +static void cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); +static void cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); +static void cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data); + static void nm_device_init (NMDevice *self) { @@ -274,6 +292,7 @@ nm_device_init (NMDevice *self) priv->dhcp_timeout = 0; priv->rfkill_type = RFKILL_TYPE_UNKNOWN; priv->autoconnect = DEFAULT_AUTOCONNECT; + priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); } static void @@ -712,6 +731,25 @@ nm_device_set_connection_provider (NMDevice *device, g_return_if_fail (priv->con_provider == NULL); priv->con_provider = provider; + priv->cp_added_id = g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_ADDED, + G_CALLBACK (cp_connection_added), + device); + + priv->cp_loaded_id = g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTIONS_LOADED, + G_CALLBACK (cp_connections_loaded), + device); + + priv->cp_removed_id = g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_REMOVED, + G_CALLBACK (cp_connection_removed), + device); + + priv->cp_updated_id = g_signal_connect (priv->con_provider, + NM_CP_SIGNAL_CONNECTION_UPDATED, + G_CALLBACK (cp_connection_updated), + device); } NMConnectionProvider * @@ -3803,6 +3841,28 @@ dispose (GObject *object) } g_free (priv->ip6_privacy_tempaddr_path); + if (priv->cp_added_id) { + g_signal_handler_disconnect (priv->con_provider, priv->cp_added_id); + priv->cp_added_id = 0; + } + + if (priv->cp_loaded_id) { + g_signal_handler_disconnect (priv->con_provider, priv->cp_loaded_id); + priv->cp_loaded_id = 0; + } + + if (priv->cp_removed_id) { + g_signal_handler_disconnect (priv->con_provider, priv->cp_removed_id); + priv->cp_removed_id = 0; + } + + if (priv->cp_updated_id) { + g_signal_handler_disconnect (priv->con_provider, priv->cp_updated_id); + priv->cp_updated_id = 0; + } + + g_hash_table_unref (priv->available_connections); + activation_source_clear (self, TRUE, AF_INET); activation_source_clear (self, TRUE, AF_INET6); @@ -3928,6 +3988,9 @@ get_property (GObject *object, guint prop_id, NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); NMDeviceState state; const char *ac_path = NULL; + GPtrArray *array; + GHashTableIter iter; + NMConnection *connection; state = nm_device_get_state (self); @@ -4020,6 +4083,13 @@ get_property (GObject *object, guint prop_id, case PROP_RFKILL_TYPE: g_value_set_uint (value, priv->rfkill_type); break; + case PROP_AVAILABLE_CONNECTIONS: + array = g_ptr_array_sized_new (g_hash_table_size (priv->available_connections)); + g_hash_table_iter_init (&iter, priv->available_connections); + while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) + g_ptr_array_add (array, g_strdup (nm_connection_get_path (connection))); + g_value_take_boxed (value, array); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -4050,6 +4120,7 @@ nm_device_class_init (NMDeviceClass *klass) klass->act_stage3_ip6_config_start = real_act_stage3_ip6_config_start; klass->act_stage4_ip4_config_timeout = real_act_stage4_ip4_config_timeout; klass->act_stage4_ip6_config_timeout = real_act_stage4_ip6_config_timeout; + klass->check_connection_available = real_check_connection_available; /* Properties */ g_object_class_install_property @@ -4229,6 +4300,14 @@ nm_device_class_init (NMDeviceClass *klass) 0, G_MAXINT, 0, G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT)); + g_object_class_install_property + (object_class, PROP_AVAILABLE_CONNECTIONS, + g_param_spec_boxed (NM_DEVICE_AVAILABLE_CONNECTIONS, + "AvailableConnections", + "AvailableConnections", + DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH, + G_PARAM_READABLE)); + /* Signals */ signals[STATE_CHANGED] = g_signal_new ("state-changed", @@ -4495,6 +4574,14 @@ nm_device_state_changed (NMDevice *device, (guint64) time (NULL), TRUE); } + if (state <= NM_DEVICE_STATE_UNAVAILABLE) + _clear_available_connections (device, TRUE); + + /* Update the available connections list when a device first becomes available */ + if ( state >= NM_DEVICE_STATE_DISCONNECTED + && old_state < NM_DEVICE_STATE_DISCONNECTED) + nm_device_recheck_available_connections (device); + /* Handle the new state here; but anything that could trigger * another state change should be done below. */ @@ -4855,3 +4942,113 @@ nm_device_get_autoconnect (NMDevice *device) return NM_DEVICE_GET_PRIVATE (device)->autoconnect; } +static void +_signal_available_connections_changed (NMDevice *device) +{ + g_object_notify (G_OBJECT (device), NM_DEVICE_AVAILABLE_CONNECTIONS); +} + +static void +_clear_available_connections (NMDevice *device, gboolean do_signal) +{ + g_hash_table_remove_all (NM_DEVICE_GET_PRIVATE (device)->available_connections); + if (do_signal == TRUE) + _signal_available_connections_changed (device); +} + +static gboolean +_try_add_available_connection (NMDevice *self, NMConnection *connection) +{ + if (nm_device_get_state (self) < NM_DEVICE_STATE_DISCONNECTED) + return FALSE; + + if (nm_device_check_connection_compatible (self, connection, NULL)) { + /* Let subclasses implement additional checks on the connection */ + if ( NM_DEVICE_GET_CLASS (self)->check_connection_available + && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection)) { + + g_hash_table_insert (NM_DEVICE_GET_PRIVATE (self)->available_connections, + g_object_ref (connection), + GUINT_TO_POINTER (1)); + } + } + return FALSE; +} + +static gboolean +_del_available_connection (NMDevice *device, NMConnection *connection) +{ + return g_hash_table_remove (NM_DEVICE_GET_PRIVATE (device)->available_connections, connection); +} + +static gboolean +real_check_connection_available (NMDevice *device, NMConnection *connection) +{ + /* Default is to assume the connection is available unless a subclass + * overrides this with more specific checks. + */ + return TRUE; +} + +void +nm_device_recheck_available_connections (NMDevice *device) +{ + NMDevicePrivate *priv; + const GSList *connections, *iter; + + g_return_if_fail (device != NULL); + g_return_if_fail (NM_IS_DEVICE (device)); + + priv = NM_DEVICE_GET_PRIVATE(device); + + _clear_available_connections (device, FALSE); + + connections = nm_connection_provider_get_connections (priv->con_provider); + for (iter = connections; iter; iter = g_slist_next (iter)) + _try_add_available_connection (device, NM_CONNECTION (iter->data)); + + _signal_available_connections_changed (device); +} + +static void +cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + if (_try_add_available_connection (NM_DEVICE (user_data), connection)) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +static void +cp_connections_loaded (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + const GSList *connections, *iter; + gboolean added = FALSE; + + connections = nm_connection_provider_get_connections (cp); + for (iter = connections; iter; iter = g_slist_next (iter)) + added |= _try_add_available_connection (NM_DEVICE (user_data), NM_CONNECTION (iter->data)); + + if (added) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +static void +cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + if (_del_available_connection (NM_DEVICE (user_data), connection)) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + +static void +cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) +{ + gboolean added, deleted; + + /* FIXME: don't remove it from the hash if it's just going to get re-added */ + deleted = _del_available_connection (NM_DEVICE (user_data), connection); + added = _try_add_available_connection (NM_DEVICE (user_data), connection); + + /* Only signal if the connection was removed OR added, but not both */ + if (added != deleted) + _signal_available_connections_changed (NM_DEVICE (user_data)); +} + diff --git a/src/nm-device.h b/src/nm-device.h index 91d9166af0..f4698928d4 100644 --- a/src/nm-device.h +++ b/src/nm-device.h @@ -59,6 +59,7 @@ #define NM_DEVICE_TYPE_DESC "type-desc" /* Internal only */ #define NM_DEVICE_RFKILL_TYPE "rfkill-type" /* Internal only */ #define NM_DEVICE_IFINDEX "ifindex" /* Internal only */ +#define NM_DEVICE_AVAILABLE_CONNECTIONS "available-connections" /* Internal signals */ #define NM_DEVICE_AUTH_REQUEST "auth-request" @@ -124,10 +125,21 @@ typedef struct { GSList *connections, char **specific_object); + /* Checks whether the connection is compatible with the device using + * only the devices type and characteristics. Does not use any live + * network information like WiFi/WiMAX scan lists etc. + */ gboolean (* check_connection_compatible) (NMDevice *self, NMConnection *connection, GError **error); + /* Checks whether the connection is likely available to be activated, + * including any live network information like scan lists. Returns + * TRUE if the connection is available; FALSE if not. + */ + gboolean (* check_connection_available) (NMDevice *self, + NMConnection *connection); + gboolean (* complete_connection) (NMDevice *self, NMConnection *connection, const char *specific_object, diff --git a/src/wimax/nm-device-wimax.c b/src/wimax/nm-device-wimax.c index 1f1a622c46..cd09758f53 100644 --- a/src/wimax/nm-device-wimax.c +++ b/src/wimax/nm-device-wimax.c @@ -254,6 +254,8 @@ remove_all_nsps (NMDeviceWimax *self) g_object_unref (nsp); } + nm_device_recheck_available_connections (NM_DEVICE (self)); + g_slist_free (priv->nsp_list); priv->nsp_list = NULL; } @@ -479,6 +481,21 @@ real_check_connection_compatible (NMDevice *device, return TRUE; } +static gboolean +real_check_connection_available (NMDevice *device, NMConnection *connection) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); + const GSList *ns_iter = NULL; + + /* Ensure the connection applies to an NSP in the scan list */ + for (ns_iter = priv->nsp_list; ns_iter; ns_iter = ns_iter->next) { + if (nm_wimax_nsp_check_compatible (NM_WIMAX_NSP (ns_iter->data), connection)) + return TRUE; + } + + return FALSE; +} + static gboolean real_complete_connection (NMDevice *device, NMConnection *connection, @@ -1070,6 +1087,9 @@ remove_outdated_nsps (NMDeviceWimax *self, g_object_unref (nsp); } + if (g_slist_length(to_remove) > 0) + nm_device_recheck_available_connections (NM_DEVICE (self)); + g_slist_free (to_remove); } @@ -1117,6 +1137,7 @@ wmx_scan_result_cb (struct wmxsdk *wmxsdk, priv->nsp_list = g_slist_append (priv->nsp_list, nsp); nm_wimax_nsp_export_to_dbus (nsp); g_signal_emit (self, signals[NSP_ADDED], 0, nsp); + nm_device_recheck_available_connections (NM_DEVICE (self)); } } } @@ -1504,6 +1525,7 @@ nm_device_wimax_class_init (NMDeviceWimaxClass *klass) device_class->hw_take_down = real_hw_take_down; device_class->update_hw_address = real_update_hw_address; device_class->check_connection_compatible = real_check_connection_compatible; + device_class->check_connection_available = real_check_connection_available; device_class->complete_connection = real_complete_connection; device_class->get_best_auto_connection = real_get_best_auto_connection; device_class->get_generic_capabilities = real_get_generic_capabilities;