Commit 24bc9531 authored by Thomas Haller's avatar Thomas Haller
parents 8c0dfd71 af6f2e49
......@@ -2703,6 +2703,14 @@ src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \
src/devices/wifi/nm-device-olpc-mesh.c \
src/devices/wifi/nm-device-olpc-mesh.h
if WITH_IWD
src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES += \
src/devices/wifi/nm-device-iwd.c \
src/devices/wifi/nm-device-iwd.h \
src/devices/wifi/nm-iwd-manager.c \
src/devices/wifi/nm-iwd-manager.h
endif
src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = \
-I$(srcdir)/src \
-I$(builddir)/src \
......
......@@ -279,6 +279,26 @@ else
AC_DEFINE(HAVE_NL80211_CRITICAL_PROTOCOL_CMDS, 0, [Define if nl80211 has critical protocol support])
fi
dnl
dnl Default to using wpa_supplicant but allow IWD as wifi backend
dnl
AC_ARG_WITH(iwd,
AS_HELP_STRING([--with-iwd=yes],
[Support IWD as wifi-backend in addition to wpa_supplicant (experimental)]),
ac_with_iwd=$withval, ac_with_iwd="no")
if test "$ac_with_iwd" != 'no'; then
ac_with_iwd='yes'
fi
if test x"$ac_with_iwd" = x"yes"; then
if test "$enable_wifi" != "yes"; then
AC_MSG_ERROR(Enabling IWD support and disabling Wi-Fi makes no sense)
fi
AC_DEFINE(WITH_IWD, 1, [Define to compile with the IWD wifi-backend])
else
AC_DEFINE(WITH_IWD, 0, [Define to compile without the IWD wifi-backend])
fi
AM_CONDITIONAL(WITH_IWD, test x"${ac_with_iwd}" = x"yes")
dnl
dnl Check for newer VLAN flags
dnl
......@@ -1417,6 +1437,7 @@ echo " ovs: $enable_ovs"
echo " libnm-glib: $with_libnm_glib"
echo " nmcli: $build_nmcli"
echo " nmtui: $build_nmtui"
echo " iwd: $ac_with_iwd"
echo
echo "Configuration plugins (main.plugins=${config_plugins_default})"
......
/* -*- 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-device-iwd.h"
#include <string.h>
#include "nm-common-macros.h"
#include "devices/nm-device.h"
#include "devices/nm-device-private.h"
#include "nm-utils.h"
#include "nm-act-request.h"
#include "nm-setting-connection.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-8021x.h"
#include "settings/nm-settings-connection.h"
#include "settings/nm-settings.h"
#include "nm-wifi-utils.h"
#include "nm-core-internal.h"
#include "nm-config.h"
#include "nm-iwd-manager.h"
#include "introspection/org.freedesktop.NetworkManager.Device.Wireless.h"
#include "devices/nm-device-logging.h"
_LOG_DECLARE_SELF(NMDeviceIwd);
static NM_CACHED_QUARK_FCN ("wireless-secrets-tries", wireless_secrets_tries_quark)
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
PROP_MODE,
PROP_BITRATE,
PROP_ACCESS_POINTS,
PROP_ACTIVE_ACCESS_POINT,
PROP_CAPABILITIES,
PROP_SCANNING,
);
enum {
ACCESS_POINT_ADDED,
ACCESS_POINT_REMOVED,
SCANNING_PROHIBITED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
GDBusObject * dbus_obj;
GDBusProxy * dbus_proxy;
GHashTable * aps;
GHashTable * new_aps;
NMWifiAP * current_ap;
GCancellable * cancellable;
NMDeviceWifiCapabilities capabilities;
NMActRequestGetSecretsCallId *wifi_secrets_id;
bool enabled:1;
bool can_scan:1;
bool scanning:1;
} NMDeviceIwdPrivate;
struct _NMDeviceIwd {
NMDevice parent;
NMDeviceIwdPrivate _priv;
};
struct _NMDeviceIwdClass {
NMDeviceClass parent;
/* Signals */
gboolean (*scanning_prohibited) (NMDeviceIwd *device, gboolean periodic);
};
/*****************************************************************************/
G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
#define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD)
/*****************************************************************************/
static void
_ap_dump (NMDeviceIwd *self,
NMLogLevel log_level,
const NMWifiAP *ap,
const char *prefix,
gint32 now_s)
{
char buf[1024];
buf[0] = '\0';
_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
prefix,
nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
}
/* Callers ensure we're not removing current_ap */
static void
ap_add_remove (NMDeviceIwd *self,
guint signum,
NMWifiAP *ap,
gboolean recheck_available_connections)
{
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
nm_assert (NM_IN_SET (signum, ACCESS_POINT_ADDED, ACCESS_POINT_REMOVED));
if (signum == ACCESS_POINT_ADDED) {
g_hash_table_insert (priv->aps,
(gpointer) nm_exported_object_export ((NMExportedObject *) ap),
g_object_ref (ap));
_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
} else
_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
g_signal_emit (self, signals[signum], 0, ap);
if (signum == ACCESS_POINT_REMOVED) {
g_hash_table_remove (priv->aps, nm_exported_object_get_path ((NMExportedObject *) ap));
nm_exported_object_unexport ((NMExportedObject *) ap);
g_object_unref (ap);
}
_notify (self, PROP_ACCESS_POINTS);
nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
if (recheck_available_connections)
nm_device_recheck_available_connections (NM_DEVICE (self));
}
static void
set_current_ap (NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
{
NMDeviceIwdPrivate *priv;
NMWifiAP *old_ap;
g_return_if_fail (NM_IS_DEVICE_IWD (self));
priv = NM_DEVICE_IWD_GET_PRIVATE (self);
old_ap = priv->current_ap;
if (old_ap == new_ap)
return;
if (new_ap)
priv->current_ap = g_object_ref (new_ap);
else
priv->current_ap = NULL;
if (old_ap) {
if (nm_wifi_ap_get_fake (old_ap))
ap_add_remove (self, ACCESS_POINT_REMOVED, old_ap, recheck_available_connections);
g_object_unref (old_ap);
}
_notify (self, PROP_ACTIVE_ACCESS_POINT);
_notify (self, PROP_MODE);
}
static gboolean
update_ap_func (gpointer key, gpointer value, gpointer user_data)
{
NMWifiAP *ap = value;
NMDeviceIwd *self = user_data;
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
NMWifiAP *new_ap = NULL;
if (priv->new_aps)
new_ap = g_hash_table_lookup (priv->new_aps,
nm_wifi_ap_get_supplicant_path (ap));
if (new_ap) {
g_hash_table_steal (priv->new_aps,
nm_wifi_ap_get_supplicant_path (ap));
if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (new_ap)))
_ap_dump (self, LOGL_TRACE, ap, "updated", 0);
g_object_unref (new_ap);
return FALSE;
}
if (ap == priv->current_ap)
/* Normally IWD will prevent the current AP from being
* removed from the list and set a low signal strength,
* but just making sure.
*/
return FALSE;
_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
g_signal_emit (self, signals[ACCESS_POINT_REMOVED], 0, ap);
nm_exported_object_unexport ((NMExportedObject *) ap);
g_object_unref (ap);
return TRUE;
}
static void
remove_all_aps (NMDeviceIwd *self)
{
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
if (!g_hash_table_size (priv->aps))
return;
set_current_ap (self, NULL, FALSE);
g_hash_table_foreach_remove (priv->aps, update_ap_func, self);
_notify (self, PROP_ACCESS_POINTS);
nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
nm_device_recheck_available_connections (NM_DEVICE (self));
}
static GVariant *
vardict_from_network_type (const gchar *type)
{
GVariantBuilder builder;
const gchar *key_mgmt = "";
const gchar *pairwise = "ccmp";
if (!strcmp (type, "psk"))
key_mgmt = "wpa-psk";
else if (!strcmp (type, "8021x"))
key_mgmt = "wpa-eap";
else
return NULL;
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&builder, "{sv}", "KeyMgmt",
g_variant_new_strv (&key_mgmt, 1));
g_variant_builder_add (&builder, "{sv}", "Pairwise",
g_variant_new_strv (&pairwise, 1));
g_variant_builder_add (&builder, "{sv}", "Group",
g_variant_new_string ("ccmp"));
return g_variant_new ("a{sv}", &builder);
}
static void
get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
{
NMDeviceIwd *self = user_data;
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *variant = NULL;
GVariantIter *networks;
const gchar *path, *name, *type;
int16_t signal;
NMWifiAP *ap;
gboolean changed = FALSE;
GHashTableIter ap_iter;
variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
G_VARIANT_TYPE ("(a(osns))"),
&error);
if (!variant) {
_LOGE (LOGD_WIFI, "Device.GetOrderedNetworks failed: %s",
error->message);
return;
}
priv->new_aps = g_hash_table_new (g_str_hash, g_str_equal);
g_variant_get (variant, "(a(osns))", &networks);
while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type)) {
GVariantBuilder builder;
gs_unref_variant GVariant *props = NULL;
GVariant *rsn;
static uint32_t ap_id = 0;
uint8_t bssid[6];
/*
* What we get from IWD are networks, or ESSs, that may
* contain multiple APs, or BSSs, each. We don't get
* information about any specific BSSs within an ESS but
* we can safely present each ESS as an individual BSS to
* NM, which will be seen as ESSs comprising a single BSS
* each. NM won't be able to handle roaming but IWD already
* does that. We fake the BSSIDs as they don't play any
* role either.
*/
bssid[0] = 0x00;
bssid[1] = 0x01;
bssid[2] = 0x02;
bssid[3] = ap_id >> 16;
bssid[4] = ap_id >> 8;
bssid[5] = ap_id++;
/* WEP not supported */
if (!strcmp (type, "wep"))
continue;
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&builder, "{sv}", "BSSID",
g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid, 6, 1));
g_variant_builder_add (&builder, "{sv}", "Mode",
g_variant_new_string ("infrastructure"));
rsn = vardict_from_network_type (type);
if (rsn)
g_variant_builder_add (&builder, "{sv}", "RSN", rsn);
props = g_variant_new ("a{sv}", &builder);
ap = nm_wifi_ap_new_from_properties (path, props);
nm_wifi_ap_set_ssid (ap, (const guint8 *) name, strlen (name));
nm_wifi_ap_set_strength (ap, nm_wifi_utils_level_to_quality (signal / 100));
nm_wifi_ap_set_freq (ap, 2417);
nm_wifi_ap_set_max_bitrate (ap, 65000);
g_hash_table_insert (priv->new_aps,
(gpointer) nm_wifi_ap_get_supplicant_path (ap),
ap);
}
g_variant_iter_free (networks);
if (g_hash_table_foreach_remove (priv->aps, update_ap_func, self))
changed = TRUE;
g_hash_table_iter_init (&ap_iter, priv->new_aps);
while (g_hash_table_iter_next (&ap_iter, NULL, (gpointer) &ap)) {
ap_add_remove (self, ACCESS_POINT_ADDED, ap, FALSE);
changed = TRUE;
}
g_hash_table_destroy (priv->new_aps);
priv->new_aps = NULL;
if (changed) {
_notify (self, PROP_ACCESS_POINTS);
nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
nm_device_recheck_available_connections (NM_DEVICE (self));
}
}
static void
update_aps (NMDeviceIwd *self)
{
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
if (!priv->cancellable)
priv->cancellable = g_cancellable_new ();
g_dbus_proxy_call (priv->dbus_proxy, "GetOrderedNetworks",
g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
2000, priv->cancellable,
get_ordered_networks_cb, self);
}
static void
send_disconnect (NMDeviceIwd *self)
{
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
}
static void
cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
{
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
set_current_ap (self, NULL, TRUE);
if (disconnect && priv->dbus_obj)
send_disconnect (self);
}
static void
deactivate (NMDevice *device)
{
cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
}
static gboolean
check_connection_compatible (NMDevice *device, NMConnection *connection)
{
NMSettingConnection *s_con;
NMSettingWireless *s_wireless;
const char *mac;
const char * const *mac_blacklist;
int i;
const char *mode;
const char *perm_hw_addr;
if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection))
return FALSE;
s_con = nm_connection_get_setting_connection (connection);
g_assert (s_con);
if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
return FALSE;
s_wireless = nm_connection_get_setting_wireless (connection);
if (!s_wireless)
return FALSE;
perm_hw_addr = nm_device_get_permanent_hw_address (device);
mac = nm_setting_wireless_get_mac_address (s_wireless);
if (perm_hw_addr) {
if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
return FALSE;
/* Check for MAC address blacklist */
mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
for (i = 0; mac_blacklist[i]; i++) {
if (!nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)) {
g_warn_if_reached ();
return FALSE;
}
if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
return FALSE;
}
} else if (mac)
return FALSE;
mode = nm_setting_wireless_get_mode (s_wireless);
if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
return FALSE;
return TRUE;
}
static NMWifiAP *
get_ap_by_path (NMDeviceIwd *self, const char *path)
{
g_return_val_if_fail (path != NULL, NULL);
return g_hash_table_lookup (NM_DEVICE_IWD_GET_PRIVATE (self)->aps, path);
}
static gboolean
check_connection_available (NMDevice *device,
NMConnection *connection,
NMDeviceCheckConAvailableFlags flags,
const char *specific_object)
{
NMDeviceIwd *self = NM_DEVICE_IWD (device);
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
NMSettingWireless *s_wifi;
const char *mode;
s_wifi = nm_connection_get_setting_wireless (connection);
g_return_val_if_fail (s_wifi, FALSE);
/* a connection that is available for a certain @specific_object, MUST
* also be available in general (without @specific_object). */
if (specific_object) {
NMWifiAP *ap;
ap = get_ap_by_path (self, specific_object);
return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
}
/* Only Infrastrusture mode at this time */
mode = nm_setting_wireless_get_mode (s_wifi);
if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
return FALSE;
/* Hidden SSIDs not supported yet */
if (nm_setting_wireless_get_hidden (s_wifi))
return FALSE;
if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
return TRUE;
/* Check at least one AP is compatible with this connection */
return !!nm_wifi_aps_find_first_compatible (priv->aps, connection, TRUE);
}
static gboolean
complete_connection (NMDevice *device,
NMConnection *connection,
const char *specific_object,
const GSList *existing_connections,
GError **error)
{
NMDeviceIwd *self = NM_DEVICE_IWD (device);
NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
NMSettingWireless *s_wifi;
const char *setting_mac;
char *str_ssid = NULL;
NMWifiAP *ap;
const GByteArray *ssid = NULL;
GByteArray *tmp_ssid = NULL;
GBytes *setting_ssid = NULL;
const char *perm_hw_addr;
const char *mode;
s_wifi = nm_connection_get_setting_wireless (connection);
mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
if (s_wifi && !nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
g_set_error_literal (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"Only Infrastructure mode is supported.");
return FALSE;
}
if (!specific_object) {
/* If not given a specific object, we need at minimum an SSID */
if (!s_wifi) {
g_set_error_literal (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"A 'wireless' setting is required if no AP path was given.");
return FALSE;
}
setting_ssid = nm_setting_wireless_get_ssid (s_wifi);
if (!setting_ssid || g_bytes_get_size (setting_ssid) == 0) {
g_set_error_literal (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"A 'wireless' setting with a valid SSID is required if no AP path was given.");
return FALSE;
}
/* Find a compatible AP in the scan list */
ap = nm_wifi_aps_find_first_compatible (priv->aps, connection, FALSE);
if (!ap) {
g_set_error_literal (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"No compatible AP in the scan list and hidden SSIDs not supported.");
return FALSE;
}
} else {
ap = get_ap_by_path (self, specific_object);
if (!ap) {
g_set_error (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
"The access point %s was not in the scan list.",
specific_object);
return FALSE;
}
}
/* Add a wifi setting if one doesn't exist yet */
if (!s_wifi) {
s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
nm_connection_add_setting (connection, NM_SETTING (s_wifi));
}
ssid = nm_wifi_ap_get_ssid (ap);
if (ssid == NULL) {
g_set_error_literal (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"A 'wireless' setting with a valid SSID is required.");
return FALSE;
}
if (!nm_wifi_ap_complete_connection (ap,
connection,
nm_wifi_utils_is_manf_default_ssid (ssid),
error)) {
if (tmp_ssid)
g_byte_array_unref (tmp_ssid);
return FALSE;
}
str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
nm_utils_complete_generic (nm_device_get_platform (device),
connection,
NM_SETTING_WIRELESS_SETTING_NAME,
existing_connections,
str_ssid,
str_ssid,
NULL,
TRUE);
g_free (str_ssid);
if (tmp_ssid)
g_byte_array_unref (tmp_ssid);
perm_hw_addr = nm_device_get_permanent_hw_address (device);
if (perm_hw_addr) {
setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
if (setting_mac) {
/* Make sure the setting MAC (if any) matches the device's permanent MAC */
if (!nm_utils_hwaddr_matches (setting_mac, -1, perm_hw_addr, -1)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
"connection does not match device");
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
return FALSE;
}
} else {
guint8 tmp[ETH_ALEN];
/* Lock the connection to this device by default if it uses a
* permanent MAC address (ie not a 'locally administered' one)
*/
nm_utils_hwaddr_aton (perm_hw_addr, tmp, ETH_ALEN);
if (!(tmp[0] & 0x02)) {
g_object_set (G_OBJECT (s_wifi),
NM_SETTING_WIRELESS_MAC_ADDRESS, perm_hw_addr,