diff options
| author | Michael Biebl <biebl@debian.org> | 2018-06-04 00:07:45 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2018-06-04 00:07:45 +0200 |
| commit | 04bc9e1cd3544445d883ad29ea108c1645c8e7b7 (patch) | |
| tree | d10c354b1b980ca8a7b9e48ec9019e8ed88bde2b /src/devices/wifi/nm-iwd-manager.c | |
| parent | ee9c73a923909e23a649407be77e25235d769e25 (diff) | |
New upstream version 1.11.4 upstream/1.11.4
Diffstat (limited to 'src/devices/wifi/nm-iwd-manager.c')
| -rw-r--r-- | src/devices/wifi/nm-iwd-manager.c | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c new file mode 100644 index 00000000..39db3a04 --- /dev/null +++ b/src/devices/wifi/nm-iwd-manager.c @@ -0,0 +1,693 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2017 Intel Corporation + */ + +#include "nm-default.h" + +#include "nm-iwd-manager.h" + +#include <string.h> +#include <net/if.h> + +#include "nm-logging.h" +#include "nm-core-internal.h" +#include "nm-manager.h" +#include "nm-device-iwd.h" +#include "nm-utils/nm-random-utils.h" + +/*****************************************************************************/ + +typedef struct { + gchar *name; + NMIwdNetworkSecurity security; +} KnownNetworkData; + +typedef struct { + NMManager *manager; + GCancellable *cancellable; + gboolean running; + GDBusObjectManager *object_manager; + guint agent_id; + gchar *agent_path; + GSList *known_networks; +} NMIwdManagerPrivate; + +struct _NMIwdManager { + GObject parent; + NMIwdManagerPrivate _priv; +}; + +struct _NMIwdManagerClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT) + +#define NM_IWD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIwdManager, NM_IS_IWD_MANAGER) + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "iwd-manager" +#define _NMLOG_DOMAIN LOGD_WIFI + +#define _NMLOG(level, ...) \ + G_STMT_START { \ + if (nm_logging_enabled (level, _NMLOG_DOMAIN)) { \ + char __prefix[32]; \ + \ + if (self) \ + g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \ + else \ + g_strlcpy (__prefix, _NMLOG_PREFIX_NAME, sizeof (__prefix)); \ + _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +static void +psk_agent_dbus_method_cb (GDBusConnection *connection, + const gchar *sender, const gchar *object_path, + const gchar *interface_name, const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager); + const gchar *network_path, *device_path, *ifname; + gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL; + gs_unref_variant GVariant *value = NULL; + gint ifindex; + NMDevice *device; + + /* Be paranoid and check the sender address */ + if (!nm_streq0 (g_dbus_object_manager_client_get_name_owner (omc), sender)) + goto return_error; + + g_variant_get (parameters, "(&o)", &network_path); + + network = g_dbus_object_manager_get_interface (priv->object_manager, + network_path, + NM_IWD_NETWORK_INTERFACE); + value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (network), "Device"); + device_path = g_variant_get_string (value, NULL); + + if (!device_path) { + _LOGE ("Device not cached for network %s in IWD Agent request", + network_path); + goto return_error; + } + + device_obj = g_dbus_object_manager_get_interface (priv->object_manager, + device_path, + NM_IWD_DEVICE_INTERFACE); + g_variant_unref (value); + value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (device_obj), "Name"); + ifname = g_variant_get_string (value, NULL); + + if (!ifname) { + _LOGE ("Name not cached for device %s in IWD Agent request", + device_path); + goto return_error; + } + + ifindex = if_nametoindex (ifname); + if (!ifindex) { + _LOGE ("if_nametoindex failed for Name %s for Device at %s: %i", + ifname, device_path, errno); + goto return_error; + } + + device = nm_manager_get_device_by_ifindex (priv->manager, ifindex); + if (!NM_IS_DEVICE_IWD (device)) { + _LOGE ("IWD device named %s is not a Wifi device in IWD Agent request", + ifname); + goto return_error; + } + + if (nm_device_iwd_agent_psk_query (NM_DEVICE_IWD (device), invocation)) + return; + + _LOGE ("Device %s did not handle the IWD Agent request", ifname); + +return_error: + /* IWD doesn't look at the specific error */ + g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "No PSK available for this connection"); +} + +static guint +psk_agent_export (GDBusConnection *connection, gpointer user_data, + gchar **agent_path, GError **error) +{ + static const GDBusArgInfo request_passphrase_arg_network = { + -1, + (gchar *) "network", + (gchar *) "o", + NULL, + }; + static const GDBusArgInfo *const request_passphrase_in_args[] = { + &request_passphrase_arg_network, + NULL, + }; + static const GDBusArgInfo request_passphrase_arg_passphrase = { + -1, + (gchar *) "passphrase", + (gchar *) "s", + NULL, + }; + static const GDBusArgInfo *const request_passphrase_out_args[] = { + &request_passphrase_arg_passphrase, + NULL, + }; + static const GDBusMethodInfo request_passphrase_info = { + -1, + (gchar *) "RequestPassphrase", + (GDBusArgInfo **) &request_passphrase_in_args, + (GDBusArgInfo **) &request_passphrase_out_args, + NULL, + }; + static const GDBusMethodInfo *const method_info[] = { + &request_passphrase_info, + NULL, + }; + static GDBusInterfaceInfo interface_info = { + -1, + (gchar *) "net.connman.iwd.Agent", + (GDBusMethodInfo **) &method_info, + NULL, + NULL, + NULL, + }; + static GDBusInterfaceVTable vtable = { + psk_agent_dbus_method_cb, + NULL, + NULL, + }; + + gchar path[50]; + unsigned int rnd; + guint id; + + if (!nm_utils_random_bytes (&rnd, sizeof (rnd))) { + g_set_error_literal (error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_FAILED, + "Can't read urandom."); + return 0; + } + + nm_sprintf_buf (path, "/agent/%u", rnd); + + id = g_dbus_connection_register_object (connection, path, + &interface_info, &vtable, + user_data, NULL, error); + + if (id) + *agent_path = g_strdup (path); + return id; +} + +static void +register_agent (NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GDBusInterface *agent_manager; + + agent_manager = g_dbus_object_manager_get_interface (priv->object_manager, + "/", + NM_IWD_AGENT_MANAGER_INTERFACE); + + /* Register our agent */ + g_dbus_proxy_call (G_DBUS_PROXY (agent_manager), + "RegisterAgent", + g_variant_new ("(o)", priv->agent_path), + G_DBUS_CALL_FLAGS_NONE, -1, + NULL, NULL, NULL); + + g_object_unref (agent_manager); +} + +/*****************************************************************************/ + +static void +set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface, + GDBusObject *object) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GDBusProxy *proxy; + GVariant *value; + const char *ifname; + gint ifindex; + NMDevice *device; + + if (!priv->running) + return; + + g_return_if_fail (G_IS_DBUS_PROXY (interface)); + + proxy = G_DBUS_PROXY (interface); + + if (strcmp (g_dbus_proxy_get_interface_name (proxy), + NM_IWD_DEVICE_INTERFACE)) + return; + + value = g_dbus_proxy_get_cached_property (proxy, "Name"); + if (!value) { + _LOGE ("Name not cached for Device at %s", + g_dbus_proxy_get_object_path (proxy)); + return; + } + + ifname = g_variant_get_string (value, NULL); + ifindex = if_nametoindex (ifname); + g_variant_unref (value); + + if (!ifindex) { + _LOGE ("if_nametoindex failed for Name %s for Device at %s: %i", + ifname, g_dbus_proxy_get_object_path (proxy), errno); + return; + } + + device = nm_manager_get_device_by_ifindex (priv->manager, ifindex); + if (!NM_IS_DEVICE_IWD (device)) { + _LOGE ("IWD device named %s is not a Wifi device", ifname); + return; + } + + nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object); +} + +static void +interface_added (GDBusObjectManager *object_manager, GDBusObject *object, + GDBusInterface *interface, gpointer user_data) +{ + NMIwdManager *self = user_data; + + set_device_dbus_object (self, interface, object); +} + +static void +interface_removed (GDBusObjectManager *object_manager, GDBusObject *object, + GDBusInterface *interface, gpointer user_data) +{ + NMIwdManager *self = user_data; + + /* + * TODO: we may need to save the GDBusInterface or GDBusObject + * pointer in the hash table because we may be no longer able to + * access the Name property or map the name to ifindex with + * if_nametoindex at this point. + */ + + set_device_dbus_object (self, interface, NULL); +} + +static gboolean +_om_has_name_owner (GDBusObjectManager *object_manager) +{ + gs_free char *name_owner = NULL; + + nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); + + name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); + return !!name_owner; +} + +static void +object_added (NMIwdManager *self, GDBusObject *object) +{ + GList *interfaces, *iter; + + interfaces = g_dbus_object_get_interfaces (object); + for (iter = interfaces; iter; iter = iter->next) { + GDBusInterface *interface = G_DBUS_INTERFACE (iter->data); + + set_device_dbus_object (self, interface, object); + } + + g_list_free_full (interfaces, g_object_unref); +} + +static void +known_network_free (KnownNetworkData *network) +{ + g_free (network->name); + g_free (network); +} + +static void +list_known_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *variant = NULL; + GVariantIter *networks, *props; + + variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, + G_VARIANT_TYPE ("(aa{sv})"), + &error); + if (!variant) { + _LOGE ("ListKnownNetworks() failed: %s", error->message); + return; + } + + g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free); + priv->known_networks = NULL; + + g_variant_get (variant, "(aa{sv})", &networks); + + while (g_variant_iter_next (networks, "a{sv}", &props)) { + const gchar *key; + const gchar *name = NULL; + const gchar *type = NULL; + GVariant *val; + KnownNetworkData *network_data; + + while (g_variant_iter_next (props, "{&sv}", &key, &val)) { + if (!strcmp (key, "Name")) + name = g_variant_get_string (val, NULL); + + if (!strcmp (key, "Type")) + type = g_variant_get_string (val, NULL); + + g_variant_unref (val); + } + + if (!name || !type) + goto next; + + network_data = g_new (KnownNetworkData, 1); + network_data->name = g_strdup (name); + if (!strcmp (type, "open")) + network_data->security = NM_IWD_NETWORK_SECURITY_NONE; + else if (!strcmp (type, "psk")) + network_data->security = NM_IWD_NETWORK_SECURITY_PSK; + else if (!strcmp (type, "8021x")) + network_data->security = NM_IWD_NETWORK_SECURITY_8021X; + + priv->known_networks = g_slist_append (priv->known_networks, + network_data); + +next: + g_variant_iter_free (props); + } + + g_variant_iter_free (networks); + + /* For completness we may want to call nm_device_emit_recheck_auto_activate + * and nm_device_recheck_available_connections for all affected devices + * now but the ListKnownNetworks call should have been really fast, + * faster than any scan on any newly created devices could have happened. + */ +} + +static void +update_known_networks (NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GDBusInterface *known_networks_if; + + known_networks_if = g_dbus_object_manager_get_interface (priv->object_manager, + "/", + NM_IWD_KNOWN_NETWORKS_INTERFACE); + + g_dbus_proxy_call (G_DBUS_PROXY (known_networks_if), + "ListKnownNetworks", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, -1, + priv->cancellable, list_known_networks_cb, self); + + g_object_unref (known_networks_if); +} + +static void prepare_object_manager (NMIwdManager *self); + +static void +name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GDBusObjectManager *object_manager = G_DBUS_OBJECT_MANAGER (object); + + nm_assert (object_manager == priv->object_manager); + + if (_om_has_name_owner (object_manager)) { + g_signal_handlers_disconnect_by_data (object_manager, self); + g_clear_object (&priv->object_manager); + prepare_object_manager (self); + } else { + const CList *tmp_lst; + NMDevice *device; + + if (!priv->running) + return; + + priv->running = false; + + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + if (NM_IS_DEVICE_IWD (device)) { + nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), + NULL); + } + } + } +} + +static void +device_added (NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GList *objects, *iter; + + if (!NM_IS_DEVICE_IWD (device)) + return; + + if (!priv->running) + return; + + objects = g_dbus_object_manager_get_objects (priv->object_manager); + for (iter = objects; iter; iter = iter->next) { + GDBusObject *object = G_DBUS_OBJECT (iter->data); + GDBusInterface *interface; + GDBusProxy *proxy; + GVariant *value; + const char *obj_ifname; + + interface = g_dbus_object_get_interface (object, + NM_IWD_DEVICE_INTERFACE); + if (!interface) + continue; + + proxy = G_DBUS_PROXY (interface); + value = g_dbus_proxy_get_cached_property (proxy, "Name"); + if (!value) { + g_object_unref (interface); + continue; + } + + obj_ifname = g_variant_get_string (value, NULL); + g_variant_unref (value); + g_object_unref (interface); + + if (strcmp (nm_device_get_iface (device), obj_ifname)) + continue; + + nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object); + break; + } + + g_list_free_full (objects, g_object_unref); +} + +static void +got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data) +{ + NMIwdManager *self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + GError *error = NULL; + GDBusObjectManager *object_manager; + GDBusConnection *connection; + + object_manager = g_dbus_object_manager_client_new_for_bus_finish (result, &error); + if (object_manager == NULL) { + _LOGE ("failed to acquire IWD Object Manager: Wi-Fi will not be available (%s)", + NM_G_ERROR_MSG (error)); + g_clear_error (&error); + return; + } + + priv->object_manager = object_manager; + + g_signal_connect (priv->object_manager, "notify::name-owner", + G_CALLBACK (name_owner_changed), self); + + nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); + + connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); + + priv->agent_id = psk_agent_export (connection, self, + &priv->agent_path, &error); + if (!priv->agent_id) { + _LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks will not work: %s", + NM_G_ERROR_MSG (error)); + g_clear_error (&error); + } + + if (_om_has_name_owner (object_manager)) { + GList *objects, *iter; + + priv->running = true; + + g_signal_connect (priv->object_manager, "interface-added", + G_CALLBACK (interface_added), self); + g_signal_connect (priv->object_manager, "interface-removed", + G_CALLBACK (interface_removed), self); + + objects = g_dbus_object_manager_get_objects (object_manager); + for (iter = objects; iter; iter = iter->next) + object_added (self, G_DBUS_OBJECT (iter->data)); + + g_list_free_full (objects, g_object_unref); + + if (priv->agent_id) + register_agent (self); + + update_known_networks (self); + } +} + +static void +prepare_object_manager (NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + + g_dbus_object_manager_client_new_for_bus (NM_IWD_BUS_TYPE, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + NM_IWD_SERVICE, "/", + NULL, NULL, NULL, + priv->cancellable, + got_object_manager, self); +} + +gboolean +nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name, + NMIwdNetworkSecurity security) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + const GSList *iter; + + for (iter = priv->known_networks; iter; iter = g_slist_next (iter)) { + const KnownNetworkData *network = iter->data; + + if (!strcmp (network->name, name) && network->security == security) + return true; + } + + return false; +} + +void +nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name, + NMIwdNetworkSecurity security) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + KnownNetworkData *network_data; + + if (nm_iwd_manager_is_known_network (self, name, security)) + return; + + network_data = g_new (KnownNetworkData, 1); + network_data->name = g_strdup (name); + network_data->security = security; + priv->known_networks = g_slist_append (priv->known_networks, network_data); +} + +/*****************************************************************************/ + +NM_DEFINE_SINGLETON_GETTER (NMIwdManager, nm_iwd_manager_get, + NM_TYPE_IWD_MANAGER); + +static void +nm_iwd_manager_init (NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + + priv->manager = g_object_ref (nm_manager_get ()); + g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, + G_CALLBACK (device_added), self); + + priv->cancellable = g_cancellable_new (); + + prepare_object_manager (self); +} + +static void +dispose (GObject *object) +{ + NMIwdManager *self = (NMIwdManager *) object; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self); + + if (priv->object_manager) { + if (priv->agent_id) { + GDBusConnection *connection; + GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager); + + /* No need to unregister the agent as IWD will detect + * our DBus connection being closed. + */ + + connection = g_dbus_object_manager_client_get_connection (omc); + + g_dbus_connection_unregister_object (connection, priv->agent_id); + priv->agent_id = 0; + } + + g_clear_object (&priv->object_manager); + } + + nm_clear_g_free (&priv->agent_path); + + nm_clear_g_cancellable (&priv->cancellable); + + g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free); + priv->known_networks = NULL; + + if (priv->manager) { + g_signal_handlers_disconnect_by_data (priv->manager, self); + g_clear_object (&priv->manager); + } + + G_OBJECT_CLASS (nm_iwd_manager_parent_class)->dispose (object); +} + +static void +nm_iwd_manager_class_init (NMIwdManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; +} |