/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2005 - 2017 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-device-wifi.h"
#include <netinet/in.h>
#include <unistd.h>
#include <linux/if_ether.h>
#include "nm-glib-aux/nm-ref-string.h"
#include "nm-glib-aux/nm-c-list.h"
#include "nm-device-wifi-p2p.h"
#include "nm-wifi-ap.h"
#include "nm-libnm-core-intern/nm-common-macros.h"
#include "devices/nm-device.h"
#include "devices/nm-device-private.h"
#include "nm-dbus-manager.h"
#include "nm-utils.h"
#include "NetworkManagerUtils.h"
#include "nm-act-request.h"
#include "supplicant/nm-supplicant-manager.h"
#include "supplicant/nm-supplicant-interface.h"
#include "supplicant/nm-supplicant-config.h"
#include "nm-setting-connection.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-8021x.h"
#include "nm-setting-ip4-config.h"
#include "nm-ip4-config.h"
#include "nm-setting-ip6-config.h"
#include "platform/nm-platform.h"
#include "nm-auth-utils.h"
#include "settings/nm-settings-connection.h"
#include "settings/nm-settings.h"
#include "nm-wifi-utils.h"
#include "nm-wifi-common.h"
#include "nm-core-internal.h"
#include "nm-config.h"
#define _NMLOG_DEVICE_TYPE NMDeviceWifi
#include "devices/nm-device-logging.h"
#define SCAN_INTERVAL_SEC_MIN 3
#define SCAN_INTERVAL_SEC_STEP 20
#define SCAN_INTERVAL_SEC_MAX 120
#define SCAN_EXTRA_DELAY_MSEC 500
#define SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC (5 * 60)
#define SCAN_REQUEST_SSIDS_MAX_NUM 32u
#define SCAN_REQUEST_SSIDS_MAX_AGE_MSEC (3 * 60 * NM_UTILS_MSEC_PER_SEC)
#define _LOGT_scan(...) _LOGT(LOGD_WIFI_SCAN, "wifi-scan: " __VA_ARGS__)
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceWifi,
PROP_MODE,
PROP_BITRATE,
PROP_ACCESS_POINTS,
PROP_ACTIVE_ACCESS_POINT,
PROP_CAPABILITIES,
PROP_SCANNING,
PROP_LAST_SCAN, );
enum {
P2P_DEVICE_CREATED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = {0};
typedef struct {
CList aps_lst_head;
GHashTable *aps_idx_by_supplicant_path;
CList scanning_prohibited_lst_head;
GCancellable *scan_request_cancellable;
GSource *scan_request_delay_source;
NMWifiAP *current_ap;
GHashTable *scan_request_ssids_hash;
CList scan_request_ssids_lst_head;
NMActRequestGetSecretsCallId *wifi_secrets_id;
NMSupplicantManager * sup_mgr;
NMSupplMgrCreateIfaceHandle *sup_create_handle;
NMSupplicantInterface * sup_iface;
gint64 scan_last_complete_msec;
gint64 scan_periodic_next_msec;
gint64 scan_last_request_started_at_msec;
guint scan_kickoff_timeout_id;
guint ap_dump_id;
guint periodic_update_id;
guint link_timeout_id;
guint reacquire_iface_id;
guint wps_timeout_id;
guint sup_timeout_id; /* supplicant association timeout */
NMDeviceWifiCapabilities capabilities;
NMSettingWirelessWakeOnWLan wowlan_restore;
NMDeviceWifiP2P *p2p_device;
NM80211Mode mode;
guint32 failed_iface_count;
gint32 hw_addr_scan_expire;
guint32 rate;
guint8 scan_periodic_interval_sec;
bool enabled : 1; /* rfkilled or not */
bool scan_is_scanning : 1;
bool scan_periodic_allowed : 1;
bool scan_explicit_allowed : 1;
bool scan_explicit_requested : 1;
bool ssid_found : 1;
bool hidden_probe_scan_warn : 1;
} NMDeviceWifiPrivate;
struct _NMDeviceWifi {
NMDevice parent;
NMDeviceWifiPrivate _priv;
};
struct _NMDeviceWifiClass {
NMDeviceClass parent;
};
/*****************************************************************************/
G_DEFINE_TYPE(NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
#define NM_DEVICE_WIFI_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI, NMDevice)
/*****************************************************************************/
static void supplicant_iface_state_down(NMDeviceWifi *self);
static void cleanup_association_attempt(NMDeviceWifi *self, gboolean disconnect);
static void supplicant_iface_state(NMDeviceWifi * self,
NMSupplicantInterfaceState new_state,
NMSupplicantInterfaceState old_state,
int disconnect_reason,
gboolean is_real_signal);
static void supplicant_iface_state_cb(NMSupplicantInterface *iface,
int new_state_i,
int old_state_i,
int disconnect_reason,
gpointer user_data);
static void supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
NMSupplicantBssInfo * bss_info,
gboolean is_present,
NMDeviceWifi * self);
static void supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
GVariant * credentials,
NMDeviceWifi * self);
static void supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
GParamSpec * pspec,
NMDeviceWifi * self);
static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
GParamSpec * pspec,
NMDeviceWifi * self);
static void periodic_update(NMDeviceWifi *self);
static void ap_add_remove(NMDeviceWifi *self,
gboolean is_adding,
NMWifiAP * ap,
gboolean recheck_available_connections);
static void _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset);
static void recheck_p2p_availability(NMDeviceWifi *self);
static void _scan_kickoff(NMDeviceWifi *self);
static gboolean _scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff);
/*****************************************************************************/
typedef struct {
GBytes *ssid;
CList lst;
gint64 timestamp_msec;
} ScanRequestSsidData;
static void
_scan_request_ssids_remove(ScanRequestSsidData *srs_data)
{
c_list_unlink_stale(&srs_data->lst);
g_bytes_unref(srs_data->ssid);
nm_g_slice_free(srs_data);
}
static void
_scan_request_ssids_remove_with_hash(NMDeviceWifiPrivate *priv, ScanRequestSsidData *srs_data)
{
nm_assert(srs_data);
nm_assert(nm_g_hash_table_lookup(priv->scan_request_ssids_hash, srs_data) == srs_data);
if (!g_hash_table_remove(priv->scan_request_ssids_hash, srs_data))
nm_assert_not_reached();
_scan_request_ssids_remove(srs_data);
}
static void
_scan_request_ssids_remove_all(NMDeviceWifiPrivate *priv,
gint64 cutoff_with_now_msec,
guint cutoff_at_len)
{
ScanRequestSsidData *srs_data;
nm_assert((!priv->scan_request_ssids_hash)
== c_list_is_empty(&priv->scan_request_ssids_lst_head));
if (!priv->scan_request_ssids_hash)
return;
if (cutoff_at_len == 0) {
nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy);
while (
(srs_data =
c_list_first_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst)))
_scan_request_ssids_remove(srs_data);
return;
}
if (cutoff_with_now_msec != 0) {
gint64 cutoff_time_msec;
/* remove all entries that are older than a max-age. */
nm_assert(cutoff_with_now_msec > 0);
cutoff_time_msec = cutoff_with_now_msec - SCAN_REQUEST_SSIDS_MAX_AGE_MSEC;
while (
(srs_data =
c_list_last_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
if (srs_data->timestamp_msec > cutoff_time_msec)
break;
_scan_request_ssids_remove_with_hash(priv, srs_data);
}
}
if (cutoff_at_len != G_MAXUINT) {
guint i;
/* trim the list to cutoff_at_len elements. */
i = nm_g_hash_table_size(priv->scan_request_ssids_hash);
for (; i > cutoff_at_len; i--) {
ScanRequestSsidData *d;
d = c_list_last_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst);
_scan_request_ssids_remove_with_hash(priv, d);
}
}
nm_assert(nm_g_hash_table_size(priv->scan_request_ssids_hash) <= SCAN_REQUEST_SSIDS_MAX_NUM);
nm_assert(nm_g_hash_table_size(priv->scan_request_ssids_hash)
== c_list_length(&priv->scan_request_ssids_lst_head));
if (c_list_is_empty(&priv->scan_request_ssids_lst_head))
nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy);
}
static GPtrArray *
_scan_request_ssids_fetch(NMDeviceWifiPrivate *priv, gint64 now_msec)
{
ScanRequestSsidData *srs_data;
GPtrArray * ssids;
guint len;
_scan_request_ssids_remove_all(priv, now_msec, G_MAXUINT);
len = nm_g_hash_table_size(priv->scan_request_ssids_hash);
if (len == 0)
return NULL;
ssids = g_ptr_array_new_full(len, (GDestroyNotify) g_bytes_unref);
nm_clear_pointer(&priv->scan_request_ssids_hash, g_hash_table_destroy);
while ((srs_data =
c_list_first_entry(&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
g_ptr_array_add(ssids, g_steal_pointer(&srs_data->ssid));
_scan_request_ssids_remove(srs_data);
}
return ssids;
}
static void
_scan_request_ssids_track(NMDeviceWifiPrivate *priv, const GPtrArray *ssids)
{
CList old_lst_head;
gint64 now_msec;
guint i;
if (!ssids || ssids->len == 0)
return;
now_msec = nm_utils_get_monotonic_timestamp_msec();
if (!priv->scan_request_ssids_hash)
priv->scan_request_ssids_hash = g_hash_table_new(nm_pgbytes_hash, nm_pgbytes_equal);
/* Do a little dance. New elements shall keep their order as in @ssids, but all
* new elements should be sorted in the list preexisting elements of the list.
* First move the old elements away, and splice them back afterwards. */
c_list_init(&old_lst_head);
c_list_splice(&old_lst_head, &priv->scan_request_ssids_lst_head);
for (i = 0; i < ssids->len; i++) {
GBytes * ssid = ssids->pdata[i];
ScanRequestSsidData *d;
G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(ScanRequestSsidData, ssid) == 0);
d = g_hash_table_lookup(priv->scan_request_ssids_hash, &ssid);
if (!d) {
d = g_slice_new(ScanRequestSsidData);
*d = (ScanRequestSsidData){
.lst = C_LIST_INIT(d->lst),
.timestamp_msec = now_msec,
.ssid = g_bytes_ref(ssid),
};
g_hash_table_add(priv->scan_request_ssids_hash, d);
} else
d->timestamp_msec = now_msec;
c_list_link_tail(&priv->scan_request_ssids_lst_head, &d->lst);
}
c_list_splice(&priv->scan_request_ssids_lst_head, &old_lst_head);
/* Trim the excess. After our splice with old_lst_head, the list contains the new
* elements (from @ssids) at the front (in there original order), followed by older elements. */
_scan_request_ssids_remove_all(priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM);
}
/*****************************************************************************/
void
nm_device_wifi_scanning_prohibited_track(NMDeviceWifi *self,
gpointer tag,
gboolean temporarily_prohibited)
{
NMDeviceWifiPrivate *priv;
NMCListElem * elem;
g_return_if_fail(NM_IS_DEVICE_WIFI(self));
nm_assert(tag);
priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
/* We track these with a simple CList. This would be not efficient, if
* there would be many users that need to be tracked at the same time (there
* aren't). In fact, most of the time there is no NMDeviceOlpcMesh and
* nobody tracks itself here. Optimize for that and simplicity. */
elem = nm_c_list_elem_find_first(&priv->scanning_prohibited_lst_head, iter, iter == tag);
if (!temporarily_prohibited) {
if (!elem)
return;
nm_c_list_elem_free(elem);
} else {
if (elem)
return;
c_list_link_tail(&priv->scanning_prohibited_lst_head, &nm_c_list_elem_new_stale(tag)->lst);
}
_scan_notify_allowed(self, NM_TERNARY_DEFAULT);
}
/*****************************************************************************/
static void
_ap_dump(NMDeviceWifi * self,
NMLogLevel log_level,
const NMWifiAP *ap,
const char * prefix,
gint64 now_msec)
{
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_msec));
}
gboolean
nm_device_wifi_get_scanning(NMDeviceWifi *self)
{
g_return_val_if_fail(NM_IS_DEVICE_WIFI(self), FALSE);
return NM_DEVICE_WIFI_GET_PRIVATE(self)->scan_is_scanning;
}
static gboolean
_scan_is_scanning_eval(NMDeviceWifiPrivate *priv)
{
return priv->scan_request_cancellable || priv->scan_request_delay_source
|| (priv->sup_iface && nm_supplicant_interface_get_scanning(priv->sup_iface));
}
static gboolean
_scan_notify_is_scanning(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gboolean last_scan_changed = FALSE;
NMDeviceState state;
gboolean scanning;
scanning = _scan_is_scanning_eval(priv);
if (scanning == priv->scan_is_scanning)
return FALSE;
priv->scan_is_scanning = scanning;
if (!scanning || priv->scan_last_complete_msec == 0) {
last_scan_changed = TRUE;
priv->scan_last_complete_msec = nm_utils_get_monotonic_timestamp_msec();
}
_LOGD(LOGD_WIFI,
"wifi-scan: scanning-state: %s%s",
scanning ? "scanning" : "idle",
last_scan_changed ? " (notify last-scan)" : "");
state = nm_device_get_state(NM_DEVICE(self));
if (scanning) {
/* while the device is activating/activated, we don't need the pending
* action. The pending action exists to delay startup complete, while
* activating that is already achieved via other means. */
if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED)
nm_device_add_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
}
nm_gobject_notify_together(self, PROP_SCANNING, last_scan_changed ? PROP_LAST_SCAN : PROP_0);
_scan_kickoff(self);
if (!_scan_is_scanning_eval(priv)) {
if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED)
nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
nm_device_remove_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
}
return TRUE;
}
static gboolean
_scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gboolean explicit_allowed;
gboolean periodic_allowed;
NMDeviceState state;
gboolean changed = FALSE;
state = nm_device_get_state(NM_DEVICE(self));
explicit_allowed = FALSE;
periodic_allowed = FALSE;
if (!c_list_is_empty(&priv->scanning_prohibited_lst_head)) {
/* something prohibits scanning. */
} else if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP)) {
/* Don't scan when a an AP or Ad-Hoc connection is active as it will
* disrupt connected clients or peers. */
} else if (NM_IN_SET(state, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_FAILED)) {
/* Can always scan when disconnected */
explicit_allowed = TRUE;
periodic_allowed = TRUE;
} else if (NM_IN_SET(state, NM_DEVICE_STATE_ACTIVATED)) {
/* Prohibit periodic scans when connected; we ask the supplicant to
* background scan for us, unless the connection is locked to a specific
* BSSID (in which case scanning is effectively disabled). */
periodic_allowed = FALSE;
/* Prohibit scans if the supplicant is busy */
if (priv->sup_iface) {
explicit_allowed = !NM_IN_SET(nm_supplicant_interface_get_state(priv->sup_iface),
NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE);
} else
explicit_allowed = FALSE;
}
if (explicit_allowed != priv->scan_explicit_allowed
|| periodic_allowed != priv->scan_periodic_allowed) {
priv->scan_periodic_allowed = periodic_allowed;
priv->scan_explicit_allowed = explicit_allowed;
_LOGT_scan("scan-periodic-allowed=%d, scan-explicit-allowed=%d",
periodic_allowed,
explicit_allowed);
changed = TRUE;
}
if (do_kickoff == NM_TERNARY_TRUE || (do_kickoff == NM_TERNARY_DEFAULT && changed))
_scan_kickoff(self);
return changed;
}
static void
supplicant_iface_notify_scanning_cb(NMSupplicantInterface *iface,
GParamSpec * pspec,
NMDeviceWifi * self)
{
_scan_notify_is_scanning(self);
}
static gboolean
unmanaged_on_quit(NMDevice *self)
{
/* Wi-Fi devices cannot be assumed and are always taken down.
* However, also when being disconnected, we scan and thus
* set the MAC address to a random value.
*
* We must restore the original MAC address when quitting, thus
* signal to unmanage the device. */
return TRUE;
}
static void
supplicant_interface_acquire_cb(NMSupplicantManager * supplicant_manager,
NMSupplMgrCreateIfaceHandle *handle,
NMSupplicantInterface * iface,
GError * error,
gpointer user_data)
{
NMDeviceWifi * self = user_data;
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (nm_utils_error_is_cancelled(error))
return;
nm_assert(priv->sup_create_handle == handle);
priv->sup_create_handle = NULL;
if (error) {
_LOGE(LOGD_WIFI, "Couldn't initialize supplicant interface: %s", error->message);
supplicant_iface_state_down(self);
nm_device_remove_pending_action(NM_DEVICE(self),
NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT,
TRUE);
return;
}
priv->sup_iface = g_object_ref(iface);
g_signal_connect(priv->sup_iface,
NM_SUPPLICANT_INTERFACE_STATE,
G_CALLBACK(supplicant_iface_state_cb),
self);
g_signal_connect(priv->sup_iface,
NM_SUPPLICANT_INTERFACE_BSS_CHANGED,
G_CALLBACK(supplicant_iface_bss_changed_cb),
self);
g_signal_connect(priv->sup_iface,
NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
G_CALLBACK(supplicant_iface_wps_credentials_cb),
self);
g_signal_connect(priv->sup_iface,
"notify::" NM_SUPPLICANT_INTERFACE_SCANNING,
G_CALLBACK(supplicant_iface_notify_scanning_cb),
self);
g_signal_connect(priv->sup_iface,
"notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS,
G_CALLBACK(supplicant_iface_notify_current_bss),
self);
g_signal_connect(priv->sup_iface,
"notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
G_CALLBACK(supplicant_iface_notify_p2p_available),
self);
_scan_notify_is_scanning(self);
if (nm_supplicant_interface_get_state(priv->sup_iface)
!= NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
/* fake an initial state change. */
supplicant_iface_state(user_data,
NM_SUPPLICANT_INTERFACE_STATE_STARTING,
nm_supplicant_interface_get_state(priv->sup_iface),
0,
FALSE);
}
}
static void
supplicant_interface_acquire(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_assert(!priv->sup_iface);
nm_assert(!priv->sup_create_handle);
priv->sup_create_handle =
nm_supplicant_manager_create_interface(priv->sup_mgr,
nm_device_get_ifindex(NM_DEVICE(self)),
NM_SUPPLICANT_DRIVER_WIRELESS,
supplicant_interface_acquire_cb,
self);
nm_device_add_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
}
static void
supplicant_interface_release(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (nm_clear_pointer(&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel))
nm_device_remove_pending_action(NM_DEVICE(self),
NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT,
TRUE);
nm_clear_g_source(&priv->scan_kickoff_timeout_id);
nm_clear_g_source_inst(&priv->scan_request_delay_source);
nm_clear_g_cancellable(&priv->scan_request_cancellable);
_scan_request_ssids_remove_all(priv, 0, 0);
priv->scan_periodic_interval_sec = 0;
priv->scan_periodic_next_msec = 0;
nm_clear_g_source(&priv->ap_dump_id);
if (priv->sup_iface) {
/* Clear supplicant interface signal handlers */
g_signal_handlers_disconnect_by_data(priv->sup_iface, self);
/* Tell the supplicant to disconnect from the current AP */
nm_supplicant_interface_disconnect(priv->sup_iface);
g_clear_object(&priv->sup_iface);
}
if (priv->p2p_device) {
/* Signal to P2P device to also release its reference */
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, NULL);
}
_scan_notify_is_scanning(self);
}
static void
update_seen_bssids_cache(NMDeviceWifi *self, NMWifiAP *ap)
{
g_return_if_fail(NM_IS_DEVICE_WIFI(self));
if (ap == NULL)
return;
/* Don't cache the BSSID for Ad-Hoc APs */
if (nm_wifi_ap_get_mode(ap) != NM_802_11_MODE_INFRA)
return;
if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_ACTIVATED
&& nm_device_has_unmodified_applied_connection(NM_DEVICE(self),
NM_SETTING_COMPARE_FLAG_NONE)) {
nm_settings_connection_add_seen_bssid(nm_device_get_settings_connection(NM_DEVICE(self)),
nm_wifi_ap_get_address(ap));
}
}
static void
set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
{
NMDeviceWifiPrivate *priv;
NMWifiAP * old_ap;
g_return_if_fail(NM_IS_DEVICE_WIFI(self));
priv = NM_DEVICE_WIFI_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);
/* Update seen BSSIDs cache */
update_seen_bssids_cache(self, priv->current_ap);
} else
priv->current_ap = NULL;
if (old_ap) {
NM80211Mode mode = nm_wifi_ap_get_mode(old_ap);
/* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
if (NM_IN_SET(mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP) || nm_wifi_ap_get_fake(old_ap))
ap_add_remove(self, FALSE, old_ap, recheck_available_connections);
g_object_unref(old_ap);
}
_notify(self, PROP_ACTIVE_ACCESS_POINT);
}
static void
periodic_update(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv;
int ifindex;
guint32 new_rate;
int percent;
if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_ACTIVATED) {
/* BSSID and signal strength have meaningful values only if the device
* is activated and not scanning.
*/
return;
}
priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (!nm_supplicant_interface_state_is_associated(
nm_supplicant_interface_get_state(priv->sup_iface))
|| nm_supplicant_interface_get_scanning(priv->sup_iface)) {
/* Only update current AP if we're actually talking to something, otherwise
* assume the old one (if any) is still valid until we're told otherwise or
* the connection fails.
*/
return;
}
if (priv->mode == NM_802_11_MODE_AP) {
/* In AP mode we currently have nothing to do. */
return;
}
ifindex = nm_device_get_ifindex(NM_DEVICE(self));
if (ifindex <= 0)
g_return_if_reached();
if (priv->current_ap
&& nm_platform_wifi_get_station(nm_device_get_platform(NM_DEVICE(self)),
ifindex,
NULL,
&percent,
&new_rate)) {
if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) {
#if NM_MORE_LOGGING
_ap_dump(self, LOGL_TRACE, priv->current_ap, "updated", 0);
#endif
}
if (new_rate != priv->rate) {
priv->rate = new_rate;
_notify(self, PROP_BITRATE);
}
}
}
static gboolean
periodic_update_cb(gpointer user_data)
{
periodic_update(user_data);
return TRUE;
}
static void
ap_add_remove(NMDeviceWifi *self,
gboolean is_adding, /* or else removing */
NMWifiAP * ap,
gboolean recheck_available_connections)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (is_adding) {
g_object_ref(ap);
ap->wifi_device = NM_DEVICE(self);
c_list_link_tail(&priv->aps_lst_head, &ap->aps_lst);
if (!g_hash_table_insert(priv->aps_idx_by_supplicant_path,
nm_wifi_ap_get_supplicant_path(ap),
ap))
nm_assert_not_reached();
nm_dbus_object_export(NM_DBUS_OBJECT(ap));
_ap_dump(self, LOGL_DEBUG, ap, "added", 0);
nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, TRUE);
} else {
ap->wifi_device = NULL;
c_list_unlink(&ap->aps_lst);
if (!g_hash_table_remove(priv->aps_idx_by_supplicant_path,
nm_wifi_ap_get_supplicant_path(ap)))
nm_assert_not_reached();
_ap_dump(self, LOGL_DEBUG, ap, "removed", 0);
}
_notify(self, PROP_ACCESS_POINTS);
if (!is_adding) {
nm_device_wifi_emit_signal_access_point(NM_DEVICE(self), ap, FALSE);
nm_dbus_object_clear_and_unexport(&ap);
}
nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
if (recheck_available_connections)
nm_device_recheck_available_connections(NM_DEVICE(self));
}
static void
remove_all_aps(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMWifiAP * ap;
if (c_list_is_empty(&priv->aps_lst_head))
return;
set_current_ap(self, NULL, FALSE);
while ((ap = c_list_first_entry(&priv->aps_lst_head, NMWifiAP, aps_lst)))
ap_add_remove(self, FALSE, ap, FALSE);
nm_device_recheck_available_connections(NM_DEVICE(self));
}
static gboolean
wake_on_wlan_restore(NMDeviceWifi *self)
{
NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMSettingWirelessWakeOnWLan w;
w = priv->wowlan_restore;
if (w == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
return TRUE;
priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET,
nm_device_get_ifindex(NM_DEVICE(self)),
w);
}
static void
disconnect_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data)
{
gs_unref_object NMDeviceWifi *self = NULL;
NMDeviceDeactivateCallback callback;
gpointer callback_user_data;
nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data);
/* error will be freed by sup_iface */
callback(NM_DEVICE(self), error, callback_user_data);
}
static void
disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable)
{
gs_unref_object NMDeviceWifi *self = NULL;
NMDeviceDeactivateCallback callback;
gpointer callback_user_data;
gs_free_error GError *cancelled_error = NULL;
nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data);
g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error);
callback(NM_DEVICE(self), cancelled_error, callback_user_data);
}
static void
deactivate_async(NMDevice * device,
GCancellable * cancellable,
NMDeviceDeactivateCallback callback,
gpointer callback_user_data)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gpointer user_data;
nm_assert(G_IS_CANCELLABLE(cancellable));
nm_assert(callback);
user_data = nm_utils_user_data_pack(g_object_ref(self), callback, callback_user_data);
if (!priv->sup_iface) {
nm_utils_invoke_on_idle(cancellable, disconnect_cb_on_idle, user_data);
return;
}
cleanup_association_attempt(self, FALSE);
nm_supplicant_interface_disconnect_async(priv->sup_iface,
cancellable,
disconnect_cb,
user_data);
}
static void
deactivate(NMDevice *device)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
int ifindex = nm_device_get_ifindex(device);
nm_clear_g_source(&priv->periodic_update_id);
cleanup_association_attempt(self, TRUE);
priv->rate = 0;
set_current_ap(self, NULL, TRUE);
if (!wake_on_wlan_restore(self))
_LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot unconfigure WoWLAN.");
/* Clear any critical protocol notification in the Wi-Fi stack */
nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE);
/* Ensure we're in infrastructure mode after deactivation; some devices
* (usually older ones) don't scan well in adhoc mode.
*/
if (nm_platform_wifi_get_mode(nm_device_get_platform(device), ifindex)
!= NM_802_11_MODE_INFRA) {
nm_device_take_down(NM_DEVICE(self), TRUE);
nm_platform_wifi_set_mode(nm_device_get_platform(device), ifindex, NM_802_11_MODE_INFRA);
nm_device_bring_up(NM_DEVICE(self), TRUE, NULL);
}
if (priv->mode != NM_802_11_MODE_INFRA) {
priv->mode = NM_802_11_MODE_INFRA;
_notify(self, PROP_MODE);
}
_scan_notify_allowed(self, NM_TERNARY_TRUE);
}
static void
deactivate_reset_hw_addr(NMDevice *device)
{
_hw_addr_set_scanning((NMDeviceWifi *) device, TRUE);
}
static gboolean
check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
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_wifi_parent_class)
->check_connection_compatible(device, connection, error))
return FALSE;
s_wireless = nm_connection_get_setting_wireless(connection);
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)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device MAC address does not match the profile");
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)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"MAC address blacklisted");
return FALSE;
}
}
} else if (mac) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device has no valid MAC address as required by profile");
return FALSE;
}
/* Early exit if supplicant or device doesn't support requested mode */
mode = nm_setting_wireless_get_mode(s_wireless);
if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) {
if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"the device does not support Ad-Hoc networks");
return FALSE;
}
} else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"the device does not support Access Point mode");
return FALSE;
}
if (priv->sup_iface) {
if (nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_AP)
== NM_TERNARY_FALSE) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"wpa_supplicant does not support Access Point mode");
return FALSE;
}
}
} else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) {
if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_MESH)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"the device does not support Mesh mode");
return FALSE;
}
if (priv->sup_iface) {
if (nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_MESH)
== NM_TERNARY_FALSE) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"wpa_supplicant does not support Mesh mode");
return FALSE;
}
}
}
// FIXME: check channel/freq/band against bands the hardware supports
// FIXME: check encryption against device capabilities
// FIXME: check bitrate against device capabilities
return TRUE;
}
static gboolean
check_connection_available(NMDevice * device,
NMConnection * connection,
NMDeviceCheckConAvailableFlags flags,
const char * specific_object,
GError ** error)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_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 = nm_wifi_ap_lookup_for_device(NM_DEVICE(self), specific_object);
if (!ap) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"requested access point not found");
return FALSE;
}
if (!nm_wifi_ap_check_compatible(ap, connection)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"requested access point is not compatible with profile");
return FALSE;
}
return TRUE;
}
/* Ad-Hoc, AP and Mesh connections are always available because they may be
* started at any time.
*/
mode = nm_setting_wireless_get_mode(s_wifi);
if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0
|| g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0
|| g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
return TRUE;
/* Hidden SSIDs obviously don't always appear in the scan list either.
*
* For an explicit user-activation-request, a connection is considered
* available because for hidden Wi-Fi, clients didn't consistently
* set the 'hidden' property to indicate hidden SSID networks. If
* activating but the network isn't available let the device recheck
* availability.
*/
if (nm_setting_wireless_get_hidden(s_wifi)
|| NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
return TRUE;
if (!nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"no compatible access point found");
return FALSE;
}
return TRUE;
}
static gboolean
complete_connection(NMDevice * device,
NMConnection * connection,
const char * specific_object,
NMConnection *const *existing_connections,
GError ** error)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMSettingWireless * s_wifi;
gs_free char * ssid_utf8 = NULL;
NMWifiAP * ap;
GBytes * ssid = NULL;
GBytes * setting_ssid = NULL;
gboolean hidden = FALSE;
const char * mode;
s_wifi = nm_connection_get_setting_wireless(connection);
mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL;
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;
}
if (!nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) {
/* Find a compatible AP in the scan list */
ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection);
/* If we still don't have an AP, then the WiFI settings needs to be
* fully specified by the client. Might not be able to find an AP
* if the network isn't broadcasting the SSID for example.
*/
if (!ap) {
if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error))
return FALSE;
hidden = TRUE;
}
} else {
if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error))
return FALSE;
ap = NULL;
}
} else if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) {
if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error))
return FALSE;
ap = NULL;
} else {
ap = nm_wifi_ap_lookup_for_device(NM_DEVICE(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));
}
if (ap)
ssid = nm_wifi_ap_get_ssid(ap);
if (ssid == NULL) {
/* The AP must be hidden. Connecting to a Wi-Fi AP requires the SSID
* as part of the initial handshake, so check the connection details
* for the SSID. The AP object will still be used for encryption
* settings and such.
*/
ssid = nm_setting_wireless_get_ssid(s_wifi);
}
if (ssid == NULL) {
/* If there's no SSID on the AP itself, and no SSID in the
* connection data, then we cannot connect at all. Return an error.
*/
g_set_error_literal(
error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
ap ? "A 'wireless' setting with a valid SSID is required for hidden access points."
: "Cannot create 'wireless' setting due to missing SSID.");
return FALSE;
}
if (ap) {
/* If the SSID is a well-known SSID, lock the connection to the AP's
* specific BSSID so NM doesn't autoconnect to some random wifi net.
*/
if (!nm_wifi_ap_complete_connection(ap,
connection,
nm_wifi_utils_is_manf_default_ssid(ssid),
error))
return FALSE;
}
ssid_utf8 = _nm_utils_ssid_to_utf8(ssid);
nm_utils_complete_generic(
nm_device_get_platform(device),
connection,
NM_SETTING_WIRELESS_SETTING_NAME,
existing_connections,
ssid_utf8,
ssid_utf8,
NULL,
nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device),
TRUE);
if (hidden)
g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
return TRUE;
}
static gboolean
is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMSupplicantInterfaceState supplicant_state;
if (!priv->enabled)
return FALSE;
if (!priv->sup_iface)
return FALSE;
supplicant_state = nm_supplicant_interface_get_state(priv->sup_iface);
if (supplicant_state <= NM_SUPPLICANT_INTERFACE_STATE_STARTING
|| supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
return FALSE;
return TRUE;
}
static gboolean
get_autoconnect_allowed(NMDevice *device)
{
return !NM_DEVICE_WIFI_GET_PRIVATE(NM_DEVICE_WIFI(device))->scan_is_scanning;
}
static gboolean
can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMConnection * connection;
NMSettingWireless * s_wifi;
NMWifiAP * ap;
const char * method6, *mode;
gboolean auto4, auto6;
nm_assert(!specific_object || !*specific_object);
if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class)->can_auto_connect(device, sett_conn, NULL))
return FALSE;
connection = nm_settings_connection_get_connection(sett_conn);
s_wifi = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wifi, FALSE);
/* Always allow autoconnect for AP and non-autoconf Ad-Hoc or Mesh */
auto4 = nm_streq0(nm_utils_get_ip_config_method(connection, AF_INET),
NM_SETTING_IP4_CONFIG_METHOD_AUTO);
method6 = nm_utils_get_ip_config_method(connection, AF_INET6);
auto6 = nm_streq0(method6, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
|| nm_streq0(method6, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
mode = nm_setting_wireless_get_mode(s_wifi);
if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP))
return TRUE;
else if (!auto4 && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_ADHOC))
return TRUE;
else if (!auto4 && !auto6 && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_MESH))
return TRUE;
ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection);
if (ap) {
/* All good; connection is usable */
NM_SET_OUT(specific_object, g_strdup(nm_dbus_object_get_path(NM_DBUS_OBJECT(ap))));
return TRUE;
}
return FALSE;
}
const CList *
_nm_device_wifi_get_aps(NMDeviceWifi *self)
{
return &NM_DEVICE_WIFI_GET_PRIVATE(self)->aps_lst_head;
}
static void
_hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
{
NMDevice * device = (NMDevice *) self;
NMDeviceWifiPrivate *priv;
guint32 now;
gboolean randomize;
g_return_if_fail(NM_IS_DEVICE_WIFI(self));
if (nm_device_is_activating(device) || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED)
return;
priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
randomize = nm_config_data_get_device_config_boolean(
NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
device,
TRUE,
TRUE);
if (!randomize) {
/* expire the temporary MAC address used during scanning */
priv->hw_addr_scan_expire = 0;
if (do_reset) {
priv->scan_last_request_started_at_msec = G_MININT64;
priv->scan_periodic_next_msec = 0;
priv->scan_periodic_interval_sec = 0;
nm_device_hw_addr_reset(device, "scanning");
}
return;
}
now = nm_utils_get_monotonic_timestamp_sec();
if (now >= priv->hw_addr_scan_expire) {
gs_free char *generate_mac_address_mask = NULL;
gs_free char *hw_addr_scan = NULL;
/* the random MAC address for scanning expires after a while.
*
* We don't bother with to update the MAC address exactly when
* it expires, instead on the next scan request, we will generate
* a new one.*/
priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC;
generate_mac_address_mask = nm_config_data_get_device_config(
NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
device,
NULL);
priv->scan_last_request_started_at_msec = G_MININT64;
priv->scan_periodic_next_msec = 0;
priv->scan_periodic_interval_sec = 0;
hw_addr_scan = nm_utils_hw_addr_gen_random_eth(nm_device_get_initial_hw_address(device),
generate_mac_address_mask);
nm_device_hw_addr_set(device, hw_addr_scan, "scanning", TRUE);
}
}
static GPtrArray *
ssids_options_to_ptrarray(GVariant *value, GError **error)
{
gs_unref_ptrarray GPtrArray *ssids = NULL;
gsize num_ssids;
gsize i;
nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aay")));
num_ssids = g_variant_n_children(value);
if (num_ssids > 32) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"too many SSIDs requested to scan");
return NULL;
}
if (num_ssids) {
ssids = g_ptr_array_new_full(num_ssids, (GDestroyNotify) g_bytes_unref);
for (i = 0; i < num_ssids; i++) {
gs_unref_variant GVariant *v = NULL;
gsize len;
const guint8 * bytes;
v = g_variant_get_child_value(value, i);
bytes = g_variant_get_fixed_array(v, &len, sizeof(guint8));
if (len > 32) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"SSID at index %d more than 32 bytes",
(int) i);
return NULL;
}
g_ptr_array_add(ssids, g_bytes_new(bytes, len));
}
}
return g_steal_pointer(&ssids);
}
GPtrArray *
nmtst_ssids_options_to_ptrarray(GVariant *value, GError **error)
{
return ssids_options_to_ptrarray(value, error);
}
static void
dbus_request_scan_cb(NMDevice * device,
GDBusMethodInvocation *context,
NMAuthSubject * subject,
GError * error,
gpointer user_data)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gs_unref_ptrarray GPtrArray *ssids = user_data;
if (error) {
g_dbus_method_invocation_return_gerror(context, error);
return;
}
_scan_request_ssids_track(priv, ssids);
priv->scan_explicit_requested = TRUE;
_scan_kickoff(self);
g_dbus_method_invocation_return_value(context, NULL);
}
void
_nm_device_wifi_request_scan(NMDeviceWifi * self,
GVariant * options,
GDBusMethodInvocation *invocation)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMDevice * device = NM_DEVICE(self);
gs_unref_ptrarray GPtrArray *ssids = NULL;
if (options) {
gs_unref_variant GVariant *val = g_variant_lookup_value(options, "ssids", NULL);
if (val) {
gs_free_error GError *ssid_error = NULL;
if (!g_variant_is_of_type(val, G_VARIANT_TYPE("aay"))) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_ARGUMENT,
"Invalid 'ssid' scan option");
return;
}
ssids = ssids_options_to_ptrarray(val, &ssid_error);
if (ssid_error) {
g_dbus_method_invocation_return_gerror(invocation, ssid_error);
return;
}
}
}
if (!priv->enabled || !priv->sup_iface
|| nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ALLOWED,
"Scanning not allowed while unavailable");
return;
}
nm_device_auth_request(device,
invocation,
NULL,
NM_AUTH_PERMISSION_WIFI_SCAN,
TRUE,
NULL,
dbus_request_scan_cb,
g_steal_pointer(&ssids));
}
static gboolean
hidden_filter_func(NMSettings *settings, NMSettingsConnection *set_con, gpointer user_data)
{
NMConnection * connection = nm_settings_connection_get_connection(set_con);
NMSettingWireless *s_wifi;
if (!nm_connection_is_type(connection, NM_SETTING_WIRELESS_SETTING_NAME))
return FALSE;
s_wifi = nm_connection_get_setting_wireless(connection);
if (!s_wifi)
return FALSE;
if (nm_streq0(nm_setting_wireless_get_mode(s_wifi), NM_SETTING_WIRELESS_MODE_AP))
return FALSE;
return nm_setting_wireless_get_hidden(s_wifi);
}
static GPtrArray *
_scan_request_ssids_build_hidden(NMDeviceWifi *self,
gint64 now_msec,
gboolean * out_has_hidden_profiles)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids(priv->sup_iface);
gs_free NMSettingsConnection **connections = NULL;
gs_unref_ptrarray GPtrArray *ssids = NULL;
gs_unref_hashtable GHashTable *unique_ssids = NULL;
guint connections_len;
guint n_hidden;
guint i;
NM_SET_OUT(out_has_hidden_profiles, FALSE);
/* collect all pending explicit SSIDs. */
ssids = _scan_request_ssids_fetch(priv, now_msec);
if (max_scan_ssids == 0) {
/* no space. @ssids will be ignored. */
return NULL;
}
if (ssids) {
if (ssids->len < max_scan_ssids) {
/* Add wildcard SSID using a static wildcard SSID used for every scan */
g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty()));
}
if (ssids->len >= max_scan_ssids) {
/* there is no more space. Use what we have. */
g_ptr_array_set_size(ssids, max_scan_ssids);
return g_steal_pointer(&ssids);
}
}
connections = nm_settings_get_connections_clone(nm_device_get_settings((NMDevice *) self),
&connections_len,
hidden_filter_func,
NULL,
NULL,
NULL);
if (!connections[0])
return g_steal_pointer(&ssids);
if (!ssids) {
ssids = g_ptr_array_new_full(max_scan_ssids, (GDestroyNotify) g_bytes_unref);
/* Add wildcard SSID using a static wildcard SSID used for every scan */
g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty()));
}
unique_ssids = g_hash_table_new(nm_gbytes_hash, nm_gbytes_equal);
for (i = 1; i < ssids->len; i++) {
if (!g_hash_table_add(unique_ssids, ssids->pdata[i]))
nm_assert_not_reached();
}
g_qsort_with_data(connections,
connections_len,
sizeof(NMSettingsConnection *),
nm_settings_connection_cmp_timestamp_p_with_data,
NULL);
n_hidden = 0;
for (i = 0; i < connections_len; i++) {
NMSettingWireless *s_wifi;
GBytes * ssid;
if (ssids->len >= max_scan_ssids)
break;
if (n_hidden > 4) {
/* we allow at most 4 hidden profiles to be actively scanned. The
* reason is speed and to not disclose too many SSIDs. */
break;
}
s_wifi = nm_connection_get_setting_wireless(
nm_settings_connection_get_connection(connections[i]));
ssid = nm_setting_wireless_get_ssid(s_wifi);
if (!g_hash_table_add(unique_ssids, ssid))
continue;
g_ptr_array_add(ssids, g_bytes_ref(ssid));
n_hidden++;
}
NM_SET_OUT(out_has_hidden_profiles, n_hidden > 0);
return g_steal_pointer(&ssids);
}
static gboolean
_scan_request_delay_cb(gpointer user_data)
{
NMDeviceWifi * self = user_data;
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_clear_g_source_inst(&priv->scan_request_delay_source);
_LOGT_scan("scan request completed (after extra delay)");
_scan_notify_is_scanning(self);
return G_SOURCE_REMOVE;
}
static void
_scan_supplicant_request_scan_cb(NMSupplicantInterface *supp_iface,
GCancellable * cancellable,
gpointer user_data)
{
NMDeviceWifi * self;
NMDeviceWifiPrivate *priv;
if (g_cancellable_is_cancelled(cancellable))
return;
self = user_data;
priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
_LOGT_scan("scan request completed (D-Bus request)");
/* we just completed a scan request, but possibly the supplicant's state is not yet toggled
* to "scanning". That means, our internal scanning state "priv->scan_is_scanning" would already
* flip to idle, while in a moment the supplicant would toggle the state again.
*
* Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */
nm_clear_g_source_inst(&priv->scan_request_delay_source);
priv->scan_request_delay_source =
nm_g_source_attach(nm_g_timeout_source_new(SCAN_EXTRA_DELAY_MSEC,
G_PRIORITY_DEFAULT,
_scan_request_delay_cb,
self,
NULL),
NULL);
g_clear_object(&priv->scan_request_cancellable);
_scan_notify_is_scanning(self);
}
static gboolean
_scan_kickoff_timeout_cb(gpointer user_data)
{
NMDeviceWifi * self = user_data;
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
priv->scan_kickoff_timeout_id = 0;
_scan_kickoff(self);
return G_SOURCE_REMOVE;
}
static void
_scan_kickoff(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gs_unref_ptrarray GPtrArray *ssids = NULL;
gboolean is_explict = FALSE;
NMDeviceState device_state;
gboolean has_hidden_profiles;
gint64 now_msec;
gint64 ratelimit_duration_msec;
if (!priv->sup_iface) {
_LOGT_scan("kickoff: don't scan (has no supplicant interface)");
return;
}
if (priv->scan_request_cancellable) {
_LOGT_scan("kickoff: don't scan (has scan_request_cancellable)");
/* We are currently waiting for a scan request to complete. Wait longer. */
return;
}
now_msec = nm_utils_get_monotonic_timestamp_msec();
_scan_request_ssids_remove_all(priv, now_msec, G_MAXUINT);
device_state = nm_device_get_state(NM_DEVICE(self));
if (device_state > NM_DEVICE_STATE_DISCONNECTED && device_state <= NM_DEVICE_STATE_ACTIVATED) {
/* while we are activated, we rate limit more. */
ratelimit_duration_msec = 8000;
} else
ratelimit_duration_msec = 1500;
if (priv->scan_last_request_started_at_msec + ratelimit_duration_msec > now_msec) {
_LOGT_scan(
"kickoff: don't scan (rate limited for another %d.%03d sec%s)",
(int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec)
/ 1000),
(int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec)
% 1000),
!priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
if (!priv->scan_kickoff_timeout_id
&& (priv->scan_explicit_allowed || priv->scan_periodic_allowed)) {
priv->scan_kickoff_timeout_id = g_timeout_add(priv->scan_last_request_started_at_msec
+ ratelimit_duration_msec - now_msec,
_scan_kickoff_timeout_cb,
self);
}
return;
}
if (priv->scan_explicit_requested) {
if (!priv->scan_explicit_allowed) {
_LOGT_scan("kickoff: don't scan (explicit scan requested but not allowed)");
return;
}
priv->scan_explicit_requested = FALSE;
is_explict = TRUE;
} else {
if (!priv->scan_periodic_allowed) {
_LOGT_scan("kickoff: don't scan (periodic scan currently not allowed)");
priv->scan_periodic_next_msec = 0;
priv->scan_periodic_interval_sec = 0;
nm_clear_g_source(&priv->scan_kickoff_timeout_id);
return;
}
nm_assert(priv->scan_explicit_allowed);
if (now_msec < priv->scan_periodic_next_msec) {
_LOGT_scan("kickoff: don't scan (periodic scan waiting for another %d.%03d sec%s)",
(int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
(int) ((priv->scan_periodic_next_msec - now_msec) % 1000),
!priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
if (!priv->scan_kickoff_timeout_id) {
priv->scan_kickoff_timeout_id =
g_timeout_add_seconds((priv->scan_periodic_next_msec - now_msec + 999) / 1000,
_scan_kickoff_timeout_cb,
self);
}
return;
}
priv->scan_periodic_interval_sec =
NM_CLAMP(((int) priv->scan_periodic_interval_sec) * 3 / 2,
SCAN_INTERVAL_SEC_MIN,
SCAN_INTERVAL_SEC_MAX);
priv->scan_periodic_next_msec = now_msec + 1000 * priv->scan_periodic_interval_sec;
}
ssids = _scan_request_ssids_build_hidden(self, now_msec, &has_hidden_profiles);
if (has_hidden_profiles) {
if (priv->hidden_probe_scan_warn) {
priv->hidden_probe_scan_warn = FALSE;
_LOGW(LOGD_WIFI,
"wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. "
"This makes you trackable");
}
} else if (!is_explict)
priv->hidden_probe_scan_warn = TRUE;
if (_LOGD_ENABLED(LOGD_WIFI)) {
gs_free char *ssids_str = NULL;
guint ssids_len = 0;
if (ssids) {
gs_strfreev char **strv = NULL;
guint i;
strv = g_new(char *, ssids->len + 1u);
for (i = 0; i < ssids->len; i++)
strv[i] = _nm_utils_ssid_to_string(ssids->pdata[i]);
strv[i] = NULL;
nm_assert(ssids->len > 0);
nm_assert(ssids->len == NM_PTRARRAY_LEN(strv));
ssids_str = g_strjoinv(", ", strv);
ssids_len = ssids->len;
}
_LOGD(LOGD_WIFI,
"wifi-scan: start %s scan (%u SSIDs to probe scan%s%s%s)",
is_explict ? "explicit" : "periodic",
ssids_len,
NM_PRINT_FMT_QUOTED(ssids_str, " [", ssids_str, "]", ""));
}
priv->scan_last_request_started_at_msec = now_msec;
if (is_explict)
_LOGT_scan("kickoff: explicit scan starting");
else {
_LOGT_scan("kickoff: periodic scan starting (next scan is scheduled in %d.%03d sec)",
(int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
(int) ((priv->scan_periodic_next_msec - now_msec) % 1000));
}
_hw_addr_set_scanning(self, FALSE);
priv->scan_request_cancellable = g_cancellable_new();
nm_supplicant_interface_request_scan(priv->sup_iface,
ssids ? (GBytes *const *) ssids->pdata : NULL,
ssids ? ssids->len : 0u,
priv->scan_request_cancellable,
_scan_supplicant_request_scan_cb,
self);
/* It's OK to call _scan_notify_is_scanning() again. They mutually call each other,
* but _scan_kickoff() sets "priv->scan_request_cancellable" which will stop
* them from recursing indefinitely. */
_scan_notify_is_scanning(self);
}
/****************************************************************************
* WPA Supplicant control stuff
*
*/
static gboolean
ap_list_dump(gpointer user_data)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(user_data);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
priv->ap_dump_id = 0;
if (_LOGD_ENABLED(LOGD_WIFI_SCAN)) {
NMWifiAP *ap;
gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
char str_buf[100];
_LOGD(LOGD_WIFI_SCAN,
"APs: [now:%u.%03u, last:%s]",
(guint)(now_msec / NM_UTILS_MSEC_PER_SEC),
(guint)(now_msec % NM_UTILS_MSEC_PER_SEC),
priv->scan_last_complete_msec > 0
? nm_sprintf_buf(str_buf,
"%u.%03u",
(guint)(priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC),
(guint)(priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC))
: "-1");
c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
_ap_dump(self, LOGL_DEBUG, ap, "dump", now_msec);
}
return G_SOURCE_REMOVE;
}
static void
schedule_ap_list_dump(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (!priv->ap_dump_id && _LOGD_ENABLED(LOGD_WIFI_SCAN))
priv->ap_dump_id = g_timeout_add_seconds(1, ap_list_dump, self);
}
static void
try_fill_ssid_for_hidden_ap(NMDeviceWifi *self, NMWifiAP *ap)
{
const char * bssid;
NMSettingsConnection *const *connections;
guint i;
g_return_if_fail(nm_wifi_ap_get_ssid(ap) == NULL);
bssid = nm_wifi_ap_get_address(ap);
g_return_if_fail(bssid);
/* Look for this AP's BSSID in the seen-bssids list of a connection,
* and if a match is found, copy over the SSID */
connections = nm_settings_get_connections(nm_device_get_settings((NMDevice *) self), NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *sett_conn = connections[i];
NMSettingWireless * s_wifi;
if (!nm_settings_connection_has_seen_bssid(sett_conn, bssid))
continue;
s_wifi =
nm_connection_get_setting_wireless(nm_settings_connection_get_connection(sett_conn));
if (!s_wifi)
continue;
nm_wifi_ap_set_ssid(ap, nm_setting_wireless_get_ssid(s_wifi));
break;
}
}
static void
supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
NMSupplicantBssInfo * bss_info,
gboolean is_present,
NMDeviceWifi * self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMWifiAP * found_ap;
GBytes * ssid;
found_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, bss_info->bss_path);
if (!is_present) {
if (!found_ap)
return;
if (found_ap == priv->current_ap) {
/* The current AP cannot be removed (to prevent NM indicating that
* it is connected, but to nothing), but it must be removed later
* when the current AP is changed or cleared. Set 'fake' to
* indicate that this AP is now unknown to the supplicant.
*/
if (nm_wifi_ap_set_fake(found_ap, TRUE))
_ap_dump(self, LOGL_DEBUG, found_ap, "updated", 0);
} else {
ap_add_remove(self, FALSE, found_ap, TRUE);
schedule_ap_list_dump(self);
}
return;
}
if (found_ap) {
if (!nm_wifi_ap_update_from_properties(found_ap, bss_info))
return;
_ap_dump(self, LOGL_DEBUG, found_ap, "updated", 0);
} else {
gs_unref_object NMWifiAP *ap = NULL;
if (!bss_info->bssid_valid) {
/* We failed to initialize the info about the AP. This can
* happen due to an error in the D-Bus communication. In this case
* we ignore the info. */
return;
}
ap = nm_wifi_ap_new_from_properties(bss_info);
/* Let the manager try to fill in the SSID from seen-bssids lists */
ssid = nm_wifi_ap_get_ssid(ap);
if (!ssid || _nm_utils_is_empty_ssid(ssid)) {
/* Try to fill the SSID from the AP database */
try_fill_ssid_for_hidden_ap(self, ap);
ssid = nm_wifi_ap_get_ssid(ap);
if (ssid && !_nm_utils_is_empty_ssid(ssid)) {
gs_free char *s = NULL;
/* Yay, matched it, no longer treat as hidden */
_LOGD(LOGD_WIFI,
"matched hidden AP %s => %s",
nm_wifi_ap_get_address(ap),
(s = _nm_utils_ssid_to_string(ssid)));
} else {
/* Didn't have an entry for this AP in the database */
_LOGD(LOGD_WIFI, "failed to match hidden AP %s", nm_wifi_ap_get_address(ap));
}
}
ap_add_remove(self, TRUE, ap, TRUE);
}
/* Update the current AP if the supplicant notified a current BSS change
* before it sent the current BSS's scan result.
*/
if (nm_supplicant_interface_get_current_bss(iface) == bss_info->bss_path)
supplicant_iface_notify_current_bss(priv->sup_iface, NULL, self);
schedule_ap_list_dump(self);
}
static void
cleanup_association_attempt(NMDeviceWifi *self, gboolean disconnect)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_clear_g_source(&priv->sup_timeout_id);
nm_clear_g_source(&priv->link_timeout_id);
nm_clear_g_source(&priv->wps_timeout_id);
if (disconnect && priv->sup_iface)
nm_supplicant_interface_disconnect(priv->sup_iface);
}
static void
cleanup_supplicant_failures(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_clear_g_source(&priv->reacquire_iface_id);
priv->failed_iface_count = 0;
}
static void
wifi_secrets_cb(NMActRequest * req,
NMActRequestGetSecretsCallId *call_id,
NMSettingsConnection * connection,
GError * error,
gpointer user_data)
{
NMDevice * device = user_data;
NMDeviceWifi * self = user_data;
NMDeviceWifiPrivate *priv;
g_return_if_fail(NM_IS_DEVICE_WIFI(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
g_return_if_fail(priv->wifi_secrets_id == call_id);
priv->wifi_secrets_id = NULL;
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_return_if_fail(req == nm_device_get_act_request(device));
g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH);
g_return_if_fail(nm_act_request_get_settings_connection(req) == connection);
if (error) {
_LOGW(LOGD_WIFI, "no secrets: %s", error->message);
/* Even if WPS is still pending, let's abort the activation when the secret
* request returns.
*
* This means, a user can only effectively use WPS when also running a secret
* agent, and pressing the push button while being prompted for the password.
* Note, that in the secret prompt the user can see that WPS is in progress
* (via the NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE flag).
*
* Previously, WPS was not cancelled when the secret request returns.
* Note that in common use-cases WPS is enabled in the connection profile
* but it won't succeed (because it's disabled in the AP or because the
* user is not prepared to press the push button).
* That means for example, during boot we would try to autoconnect with WPS.
* At that point, there is no secret-agent running, and WPS is pending for
* full 30 seconds. If in the meantime a secret agent registers (because
* of logging into the DE), the profile is still busy waiting for WPS to time
* out. Only after that delay, autoconnect starts again (note that autoconnect gets
* not blocked in this case, because a secret agent registered in the meantime).
*
* It seems wrong to continue doing WPS if the user is not aware
* that WPS is ongoing. The user is required to perform an action (push button),
* and must be told via the secret prompt.
* If no secret-agent is running, if the user cancels the secret-request, or any
* other error to obtain secrets, the user apparently does not want WPS either.
*/
nm_clear_g_source(&priv->wps_timeout_id);
nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
return;
}
nm_device_activate_schedule_stage1_device_prepare(device, FALSE);
}
static void
wifi_secrets_cancel(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
if (priv->wifi_secrets_id)
nm_act_request_cancel_secrets(NULL, priv->wifi_secrets_id);
nm_assert(!priv->wifi_secrets_id);
}
static void
supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
GVariant * credentials,
NMDeviceWifi * self)
{
NMActRequest * req;
gs_unref_variant GVariant *val_key = NULL;
gs_unref_variant GVariant *secrets = NULL;
gs_free_error GError *error = NULL;
const char * array;
gsize psk_len = 0;
if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_NEED_AUTH) {
_LOGI(LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
return;
}
_LOGI(LOGD_DEVICE | LOGD_WIFI, "WPS: Updating the connection with credentials");
req = nm_device_get_act_request(NM_DEVICE(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
val_key = g_variant_lookup_value(credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
if (val_key) {
char psk[64];
array = g_variant_get_fixed_array(val_key, &psk_len, 1);
if (psk_len >= 8 && psk_len <= 63) {
memcpy(psk, array, psk_len);
psk[psk_len] = '\0';
if (g_utf8_validate(psk, psk_len, NULL)) {
secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]",
NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
NM_SETTING_WIRELESS_SECURITY_PSK,
psk);
g_variant_ref_sink(secrets);
}
}
if (!secrets)
_LOGW(LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
}
if (!secrets)
return;
if (!nm_settings_connection_new_secrets(nm_act_request_get_settings_connection(req),
nm_act_request_get_applied_connection(req),
NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
secrets,
&error)) {
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"WPS: Could not update the connection with credentials: %s",
error->message);
return;
}
wifi_secrets_cancel(self);
nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE);
}
static gboolean
wps_timeout_cb(gpointer user_data)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(user_data);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
priv->wps_timeout_id = 0;
if (!priv->wifi_secrets_id) {
/* Fail only if the secrets are not being requested. */
nm_device_state_changed(NM_DEVICE(self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NO_SECRETS);
}
return G_SOURCE_REMOVE;
}
static void
wifi_secrets_get_secrets(NMDeviceWifi * self,
const char * setting_name,
NMSecretAgentGetSecretsFlags flags)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMActRequest * req;
wifi_secrets_cancel(self);
req = nm_device_get_act_request(NM_DEVICE(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
priv->wifi_secrets_id =
nm_act_request_get_secrets(req, TRUE, setting_name, flags, NULL, wifi_secrets_cb, self);
g_return_if_fail(priv->wifi_secrets_id);
}
/*
* link_timeout_cb
*
* Called when the link to the access point has been down for a specified
* period of time.
*/
static gboolean
link_timeout_cb(gpointer user_data)
{
NMDevice * device = NM_DEVICE(user_data);
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
_LOGW(LOGD_WIFI, "link timed out.");
priv->link_timeout_id = 0;
/* Disconnect event while activated; the supplicant hasn't been able
* to reassociate within the timeout period, so the connection must
* fail.
*/
if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED)
return FALSE;
set_current_ap(self, NULL, TRUE);
nm_device_state_changed(device,
NM_DEVICE_STATE_FAILED,
priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT
: NM_DEVICE_STATE_REASON_SSID_NOT_FOUND);
return FALSE;
}
static gboolean
need_new_8021x_secrets(NMDeviceWifi * self,
NMSupplicantInterfaceState old_state,
const char ** setting_name)
{
NMSetting8021x * s_8021x;
NMSettingWirelessSecurity *s_wsec;
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
NMConnection * connection;
g_return_val_if_fail(setting_name, FALSE);
connection = nm_device_get_applied_connection(NM_DEVICE(self));
g_return_val_if_fail(connection != NULL, FALSE);
/* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's
* attempting to authenticate with the AP.
*/
if (old_state != NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED)
return FALSE;
/* If it's an 802.1x or LEAP connection with "always ask"/unsaved secrets
* then we need to ask again because it might be an OTP token and the PIN
* may have changed.
*/
s_8021x = nm_connection_get_setting_802_1x(connection);
if (s_8021x) {
if (!nm_setting_get_secret_flags(NM_SETTING(s_8021x),
NM_SETTING_802_1X_PASSWORD,
&secret_flags,
NULL))
g_assert_not_reached();
if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
*setting_name = NM_SETTING_802_1X_SETTING_NAME;
return *setting_name ? TRUE : FALSE;
}
s_wsec = nm_connection_get_setting_wireless_security(connection);
if (s_wsec) {
if (!nm_setting_get_secret_flags(NM_SETTING(s_wsec),
NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
&secret_flags,
NULL))
g_assert_not_reached();
if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
*setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
return *setting_name ? TRUE : FALSE;
}
/* Not a LEAP or 802.1x connection */
return FALSE;
}
static gboolean
need_new_wpa_psk(NMDeviceWifi * self,
NMSupplicantInterfaceState old_state,
int disconnect_reason,
const char ** setting_name)
{
NMSettingWirelessSecurity *s_wsec;
NMConnection * connection;
const char * key_mgmt = NULL;
g_return_val_if_fail(setting_name, FALSE);
connection = nm_device_get_applied_connection(NM_DEVICE(self));
g_return_val_if_fail(connection, FALSE);
/* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */
if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE)
return FALSE;
s_wsec = nm_connection_get_setting_wireless_security(connection);
if (s_wsec)
key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
if (g_strcmp0(key_mgmt, "wpa-psk") == 0) {
/* -4 (locally-generated WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY) usually
* means the driver missed beacons from the AP. This usually happens
* due to driver bugs or faulty power-save management. It doesn't
* indicate that the PSK is wrong.
*/
#define LOCAL_WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY -4
if (disconnect_reason == LOCAL_WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY)
return FALSE;
*setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
return TRUE;
}
/* Not a WPA-PSK connection */
return FALSE;
}
static gboolean
handle_8021x_or_psk_auth_fail(NMDeviceWifi * self,
NMSupplicantInterfaceState new_state,
NMSupplicantInterfaceState old_state,
int disconnect_reason)
{
NMDevice * device = NM_DEVICE(self);
NMActRequest *req;
const char * setting_name = NULL;
gboolean handled = FALSE;
g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE);
req = nm_device_get_act_request(NM_DEVICE(self));
g_return_val_if_fail(req != NULL, FALSE);
if (need_new_8021x_secrets(self, old_state, &setting_name)
|| need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) {
nm_act_request_clear_secrets(req);
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) disconnected during association, asking for new key");
cleanup_association_attempt(self, TRUE);
nm_device_state_changed(device,
NM_DEVICE_STATE_NEED_AUTH,
NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
wifi_secrets_get_secrets(self,
setting_name,
NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
| NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
handled = TRUE;
}
return handled;
}
static gboolean
reacquire_interface_cb(gpointer user_data)
{
NMDevice * device = NM_DEVICE(user_data);
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
priv->reacquire_iface_id = 0;
priv->failed_iface_count++;
_LOGW(LOGD_WIFI, "re-acquiring supplicant interface (#%d).", priv->failed_iface_count);
if (!priv->sup_iface)
supplicant_interface_acquire(self);
return G_SOURCE_REMOVE;
}
static void
supplicant_iface_state_down(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMDevice * device = NM_DEVICE(self);
nm_device_queue_recheck_available(device,
NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
cleanup_association_attempt(self, FALSE);
/* If the device is already in UNAVAILABLE state then the state change
* is a NOP and the interface won't be re-acquired in the device state
* change handler. So ensure we have a new one here so that we're
* ready if the supplicant comes back.
*/
supplicant_interface_release(self);
if (priv->failed_iface_count < 5)
priv->reacquire_iface_id = g_timeout_add_seconds(10, reacquire_interface_cb, self);
else
_LOGI(LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
}
static void
supplicant_iface_state(NMDeviceWifi * self,
NMSupplicantInterfaceState new_state,
NMSupplicantInterfaceState old_state,
int disconnect_reason,
gboolean is_real_signal)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMDevice * device = NM_DEVICE(self);
NMDeviceState devstate;
gboolean scanning;
gboolean scan_changed;
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"supplicant interface state: %s -> %s%s",
nm_supplicant_interface_state_to_string(old_state),
nm_supplicant_interface_state_to_string(new_state),
is_real_signal ? "" : " (simulated signal)");
if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
supplicant_iface_state_down(self);
goto out;
}
devstate = nm_device_get_state(device);
scanning = nm_supplicant_interface_get_scanning(priv->sup_iface);
if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
_LOGD(LOGD_WIFI, "supplicant ready");
nm_device_queue_recheck_available(NM_DEVICE(device),
NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
priv->scan_periodic_interval_sec = 0;
priv->scan_periodic_next_msec = 0;
}
/* In these states we know the supplicant is actually talking to something */
if (new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
&& new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
priv->ssid_found = TRUE;
if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
recheck_p2p_availability(self);
switch (new_state) {
case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
nm_clear_g_source(&priv->sup_timeout_id);
nm_clear_g_source(&priv->link_timeout_id);
nm_clear_g_source(&priv->wps_timeout_id);
/* If this is the initial association during device activation,
* schedule the next activation stage.
*/
if (devstate == NM_DEVICE_STATE_CONFIG) {
NMSettingWireless *s_wifi;
GBytes * ssid;
gs_free char * ssid_str = NULL;
s_wifi = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS);
g_return_if_fail(s_wifi);
ssid = nm_setting_wireless_get_ssid(s_wifi);
g_return_if_fail(ssid);
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) Stage 2 of 5 (Device Configure) successful. %s %s",
priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot"
: "Connected to wireless network",
(ssid_str = _nm_utils_ssid_to_string(ssid)));
nm_device_activate_schedule_stage3_ip_config_start(device);
} else if (devstate == NM_DEVICE_STATE_ACTIVATED)
periodic_update(self);
break;
case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating(device)) {
/* Disconnect of an 802.1x/LEAP connection during authentication,
* or disconnect of a WPA-PSK connection during the 4-way handshake,
* often means secrets are wrong. Not always the case, but until we
* have more information from wpa_supplicant about why the
* disconnect happened this is the best we can do.
*/
if (handle_8021x_or_psk_auth_fail(self, new_state, old_state, disconnect_reason))
break;
}
/* Otherwise, it might be a stupid driver or some transient error, so
* let the supplicant try to reconnect a few more times. Give it more
* time if a scan is in progress since the link might be dropped during
* the scan but will be re-established when the scan is done.
*/
if (devstate == NM_DEVICE_STATE_ACTIVATED) {
if (priv->link_timeout_id == 0) {
priv->link_timeout_id =
g_timeout_add_seconds(scanning ? 30 : 15, link_timeout_cb, self);
priv->ssid_found = FALSE;
}
}
break;
case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
/* we would clear _scan_has_pending_action_set() and trigger a new scan.
* However, we don't want to cancel the current pending action, so force
* a new scan request. */
break;
default:
break;
}
out:
scan_changed = _scan_notify_allowed(self, NM_TERNARY_FALSE);
scan_changed |= _scan_notify_is_scanning(self);
if (scan_changed)
_scan_kickoff(self);
if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
nm_device_remove_pending_action(device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
}
static void
supplicant_iface_state_cb(NMSupplicantInterface *iface,
int new_state_i,
int old_state_i,
int disconnect_reason,
gpointer user_data)
{
supplicant_iface_state(user_data, new_state_i, old_state_i, disconnect_reason, TRUE);
}
static void
supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data)
{
NMDeviceWifi *self = NM_DEVICE_WIFI(user_data);
NMDevice * device = NM_DEVICE(self);
if (error && !nm_utils_error_is_cancelled_or_disposing(error)
&& nm_device_is_activating(device)) {
cleanup_association_attempt(self, TRUE);
nm_device_queue_state(device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
}
}
static void
supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
GParamSpec * pspec,
NMDeviceWifi * self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMRefString * current_bss;
NMWifiAP * new_ap = NULL;
current_bss = nm_supplicant_interface_get_current_bss(iface);
if (current_bss)
new_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, current_bss);
if (new_ap != priv->current_ap) {
const char * new_bssid = NULL;
GBytes * new_ssid = NULL;
const char * old_bssid = NULL;
GBytes * old_ssid = NULL;
gs_free char *new_ssid_s = NULL;
gs_free char *old_ssid_s = NULL;
/* Don't ever replace a "fake" current AP if we don't know about the
* supplicant's current BSS yet. It'll get replaced when we receive
* the current BSS's scan result.
*/
if (new_ap == NULL && nm_wifi_ap_get_fake(priv->current_ap))
return;
if (new_ap) {
new_bssid = nm_wifi_ap_get_address(new_ap);
new_ssid = nm_wifi_ap_get_ssid(new_ap);
}
if (priv->current_ap) {
old_bssid = nm_wifi_ap_get_address(priv->current_ap);
old_ssid = nm_wifi_ap_get_ssid(priv->current_ap);
}
_LOGD(LOGD_WIFI,
"roamed from BSSID %s (%s) to %s (%s)",
old_bssid ?: "(none)",
(old_ssid_s = _nm_utils_ssid_to_string(old_ssid)),
new_bssid ?: "(none)",
(new_ssid_s = _nm_utils_ssid_to_string(new_ssid)));
if (new_bssid) {
/* The new AP could be in a different layer 3 network
* and so the old DHCP lease could be no longer valid.
* Also, some APs (e.g. Cisco) can be configured to drop
* all traffic until DHCP completes. To support such
* cases, renew the lease when roaming to a new AP. */
nm_device_update_dynamic_ip_setup(NM_DEVICE(self));
}
set_current_ap(self, new_ap, TRUE);
}
}
/* We bind the existence of the P2P device to a wifi device that is being
* managed by NetworkManager and is capable of P2P operation.
* Note that some care must be taken here, because we don't want to re-create
* the device every time the supplicant interface is destroyed (e.g. due to
* a suspend/resume cycle).
* Therefore, this function will be called when a change in the P2P capability
* is detected and the supplicant interface has been initialised.
*/
static void
recheck_p2p_availability(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gboolean p2p_available;
g_object_get(priv->sup_iface, NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available, NULL);
if (p2p_available && !priv->p2p_device) {
gs_free char *iface_name = NULL;
/* Create a P2P device. "p2p-dev-" is the same prefix as chosen by
* wpa_supplicant internally.
*/
iface_name = g_strconcat("p2p-dev-", nm_device_get_iface(NM_DEVICE(self)), NULL);
priv->p2p_device = nm_device_wifi_p2p_new(iface_name);
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface);
g_signal_emit(self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
g_object_add_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device);
g_object_unref(priv->p2p_device);
return;
}
if (p2p_available && priv->p2p_device) {
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface);
return;
}
if (!p2p_available && priv->p2p_device) {
/* Destroy the P2P device. */
g_object_remove_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device);
nm_device_wifi_p2p_remove(g_steal_pointer(&priv->p2p_device));
return;
}
}
static void
supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
GParamSpec * pspec,
NMDeviceWifi * self)
{
if (nm_supplicant_interface_get_state(iface) > NM_SUPPLICANT_INTERFACE_STATE_STARTING)
recheck_p2p_availability(self);
}
static gboolean
handle_auth_or_fail(NMDeviceWifi *self, NMActRequest *req, gboolean new_secrets)
{
NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
const char * setting_name;
NMConnection * applied_connection;
NMSettingWirelessSecurity * s_wsec;
const char * bssid = NULL;
NM80211ApFlags ap_flags;
NMSettingWirelessSecurityWpsMethod wps_method;
const char * type;
NMSecretAgentGetSecretsFlags get_secret_flags =
NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
g_return_val_if_fail(NM_IS_DEVICE_WIFI(self), FALSE);
if (!req) {
req = nm_device_get_act_request(NM_DEVICE(self));
g_return_val_if_fail(req, FALSE);
}
if (!nm_device_auth_retries_try_next(NM_DEVICE(self)))
return FALSE;
nm_device_state_changed(NM_DEVICE(self),
NM_DEVICE_STATE_NEED_AUTH,
NM_DEVICE_STATE_REASON_NONE);
applied_connection = nm_act_request_get_applied_connection(req);
s_wsec = nm_connection_get_setting_wireless_security(applied_connection);
wps_method = nm_setting_wireless_security_get_wps_method(s_wsec);
/* Negotiate the WPS method */
if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
wps_method = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO;
if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO && priv->current_ap) {
/* Determine the method to use from AP capabilities. */
ap_flags = nm_wifi_ap_get_flags(priv->current_ap);
if (ap_flags & NM_802_11_AP_FLAGS_WPS_PBC)
wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
if (ap_flags & NM_802_11_AP_FLAGS_WPS_PIN)
wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
if (ap_flags & NM_802_11_AP_FLAGS_WPS
&& wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO) {
/* The AP doesn't specify which methods are supported. Allow all. */
wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
}
}
if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC) {
get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE;
type = "pbc";
} else if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
type = "pin";
} else
type = NULL;
if (type) {
priv->wps_timeout_id = g_timeout_add_seconds(30, wps_timeout_cb, self);
if (priv->current_ap)
bssid = nm_wifi_ap_get_address(priv->current_ap);
nm_supplicant_interface_enroll_wps(priv->sup_iface, type, bssid, NULL);
}
nm_act_request_clear_secrets(req);
setting_name = nm_connection_need_secrets(applied_connection, NULL);
if (!setting_name) {
_LOGW(LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
return FALSE;
}
if (new_secrets)
get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
wifi_secrets_get_secrets(self, setting_name, get_secret_flags);
return TRUE;
}
/*
* supplicant_connection_timeout_cb
*
* Called when the supplicant has been unable to connect to an access point
* within a specified period of time.
*/
static gboolean
supplicant_connection_timeout_cb(gpointer user_data)
{
NMDevice * device = NM_DEVICE(user_data);
NMDeviceWifi * self = NM_DEVICE_WIFI(user_data);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMActRequest * req;
NMConnection * connection;
cleanup_association_attempt(self, TRUE);
if (!nm_device_is_activating(device))
return FALSE;
/* Timed out waiting for a successful connection to the AP; if the AP's
* security requires network-side authentication (like WPA or 802.1x)
* and the connection attempt timed out then it's likely the authentication
* information (passwords, pin codes, etc) are wrong.
*/
req = nm_device_get_act_request(device);
g_assert(req);
connection = nm_act_request_get_applied_connection(req);
g_assert(connection);
if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH, NM_802_11_MODE_AP)) {
/* In Ad-Hoc and AP modes there's nothing to check the encryption key
* (if any), so supplicant timeouts here are almost certainly the wifi
* driver being really stupid.
*/
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) %s network creation took too long, failing activation",
priv->mode == NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot");
nm_device_state_changed(device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
return FALSE;
}
g_assert(priv->mode == NM_802_11_MODE_INFRA);
if (priv->ssid_found && nm_connection_get_setting_wireless_security(connection)) {
guint64 timestamp = 0;
gboolean new_secrets = TRUE;
/* Connection failed; either driver problems, the encryption key is
* wrong, or the passwords or certificates were wrong.
*/
_LOGW(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) association took too long");
/* Ask for new secrets only if we've never activated this connection
* before. If we've connected before, don't bother the user with
* dialogs, just retry or fail, and if we never connect the user can
* fix the password somewhere else.
*/
if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req),
×tamp))
new_secrets = !timestamp;
if (handle_auth_or_fail(self, req, new_secrets))
_LOGW(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets");
else {
nm_device_state_changed(device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NO_SECRETS);
}
} else {
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) association took too long, failing activation");
nm_device_state_changed(device,
NM_DEVICE_STATE_FAILED,
priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT
: NM_DEVICE_STATE_REASON_SSID_NOT_FOUND);
}
return FALSE;
}
static NMSupplicantConfig *
build_supplicant_config(NMDeviceWifi *self,
NMConnection *connection,
guint32 fixed_freq,
GError ** error)
{
NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMSupplicantConfig * config = NULL;
NMSettingWireless * s_wireless;
NMSettingWirelessSecurity * s_wireless_sec;
NMSettingWirelessSecurityPmf pmf;
NMSettingWirelessSecurityFils fils;
NMTernary ap_isolation;
g_return_val_if_fail(priv->sup_iface, NULL);
s_wireless = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wireless != NULL, NULL);
config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface));
/* Warn if AP mode may not be supported */
if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP)
&& nm_supplicant_interface_get_capability(priv->sup_iface, NM_SUPPL_CAP_TYPE_AP)
!= NM_TERNARY_TRUE) {
_LOGW(LOGD_WIFI, "Supplicant may not support AP mode; connection may time out.");
}
if (!nm_supplicant_config_add_setting_wireless(config, s_wireless, fixed_freq, error)) {
g_prefix_error(error, "802-11-wireless: ");
goto error;
}
if (!nm_supplicant_config_add_bgscan(config, connection, error)) {
g_prefix_error(error, "bgscan: ");
goto error;
}
ap_isolation = nm_setting_wireless_get_ap_isolation(s_wireless);
if (ap_isolation == NM_TERNARY_DEFAULT) {
ap_isolation = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
"wifi.ap-isolation",
NM_DEVICE(self),
NM_TERNARY_FALSE,
NM_TERNARY_TRUE,
NM_TERNARY_FALSE);
}
nm_supplicant_config_set_ap_isolation(config, ap_isolation == NM_TERNARY_TRUE);
s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
if (s_wireless_sec) {
NMSetting8021x *s_8021x;
const char * con_uuid = nm_connection_get_uuid(connection);
guint32 mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self)));
g_assert(con_uuid);
/* Configure PMF (802.11w) */
pmf = nm_setting_wireless_security_get_pmf(s_wireless_sec);
if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
pmf = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
"wifi-sec.pmf",
NM_DEVICE(self),
NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
}
/* Configure FILS (802.11ai) */
fils = nm_setting_wireless_security_get_fils(s_wireless_sec);
if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
fils = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
"wifi-sec.fils",
NM_DEVICE(self),
NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE,
NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
}
s_8021x = nm_connection_get_setting_802_1x(connection);
if (!nm_supplicant_config_add_setting_wireless_security(config,
s_wireless_sec,
s_8021x,
con_uuid,
mtu,
pmf,
fils,
error)) {
g_prefix_error(error, "802-11-wireless-security: ");
goto error;
}
} else {
if (!nm_supplicant_config_add_no_security(config, error)) {
g_prefix_error(error, "unsecured-option: ");
goto error;
}
}
return config;
error:
g_object_unref(config);
return NULL;
}
/*****************************************************************************/
static gboolean
wake_on_wlan_enable(NMDeviceWifi *self)
{
NMDeviceWifiPrivate * priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMSettingWirelessWakeOnWLan wowl;
NMSettingWireless * s_wireless;
s_wireless = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS);
if (s_wireless) {
wowl = nm_setting_wireless_get_wake_on_wlan(s_wireless);
if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
goto found;
}
wowl = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
"wifi.wake-on-wlan",
NM_DEVICE(self),
NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE,
G_MAXINT32,
NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT);
if (NM_FLAGS_ANY(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) {
if (!nm_utils_is_power_of_two(wowl)) {
_LOGD(LOGD_WIFI,
"invalid default value %u for wake-on-wlan: "
"'default' and 'ignore' are exclusive flags",
(guint) wowl);
wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
}
} else if (NM_FLAGS_ANY(wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) {
_LOGD(LOGD_WIFI, "invalid default value %u for wake-on-wlan", (guint) wowl);
wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
}
if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
goto found;
wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
found:
if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) {
priv->wowlan_restore = wowl;
return TRUE;
}
priv->wowlan_restore =
nm_platform_wifi_get_wake_on_wlan(NM_PLATFORM_GET, nm_device_get_ifindex(NM_DEVICE(self)));
return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET,
nm_device_get_ifindex(NM_DEVICE(self)),
wowl);
}
static NMActStageReturn
act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMWifiAP * ap = NULL;
gs_unref_object NMWifiAP *ap_fake = NULL;
NMActRequest * req;
NMConnection * connection;
NMSettingWireless * s_wireless;
const char * mode;
const char * ap_path;
req = nm_device_get_act_request(NM_DEVICE(self));
g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
connection = nm_act_request_get_applied_connection(req);
g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
s_wireless = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
nm_supplicant_interface_cancel_wps(priv->sup_iface);
mode = nm_setting_wireless_get_mode(s_wireless);
if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
priv->mode = NM_802_11_MODE_INFRA;
else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0)
priv->mode = NM_802_11_MODE_ADHOC;
else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
priv->mode = NM_802_11_MODE_AP;
/* Scanning not done in AP mode; clear the scan list */
remove_all_aps(self);
} else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
priv->mode = NM_802_11_MODE_MESH;
_notify(self, PROP_MODE);
/* expire the temporary MAC address used during scanning */
priv->hw_addr_scan_expire = 0;
/* Set spoof MAC to the interface */
if (!nm_device_hw_addr_set_cloned(device, connection, TRUE)) {
*out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
return NM_ACT_STAGE_RETURN_FAILURE;
}
/* AP and Mesh modes never use a specific object or existing scanned AP */
if (!NM_IN_SET(priv->mode, NM_802_11_MODE_AP, NM_802_11_MODE_MESH)) {
ap_path = nm_active_connection_get_specific_object(NM_ACTIVE_CONNECTION(req));
ap = ap_path ? nm_wifi_ap_lookup_for_device(NM_DEVICE(self), ap_path) : NULL;
}
if (!ap)
ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection);
if (!ap) {
/* If the user is trying to connect to an AP that NM doesn't yet know about
* (hidden network or something), starting a Hotspot or joining a Mesh,
* create a fake APfrom the security settings in the connection. This "fake"
* AP gets used until the real one is found in the scan list (Ad-Hoc or Hidden),
* or until the device is deactivated (Hotspot).
*/
ap_fake = nm_wifi_ap_new_fake_from_connection(connection);
if (!ap_fake)
g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE);
if (nm_wifi_ap_is_hotspot(ap_fake))
nm_wifi_ap_set_address(ap_fake, nm_device_get_hw_address(device));
g_object_freeze_notify(G_OBJECT(self));
ap_add_remove(self, TRUE, ap_fake, TRUE);
g_object_thaw_notify(G_OBJECT(self));
ap = ap_fake;
}
_scan_notify_allowed(self, NM_TERNARY_DEFAULT);
set_current_ap(self, ap, FALSE);
nm_active_connection_set_specific_object(NM_ACTIVE_CONNECTION(req),
nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)));
return NM_ACT_STAGE_RETURN_SUCCESS;
}
static void
ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap)
{
NMDevice * device = NM_DEVICE(self);
const char * band = nm_setting_wireless_get_band(s_wifi);
const guint32 a_freqs[] = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0};
const guint32 bg_freqs[] = {2412, 2437, 2462, 2472, 0};
guint32 freq = 0;
g_assert(ap);
if (nm_wifi_ap_get_freq(ap))
return;
if (g_strcmp0(band, "a") == 0)
freq = nm_platform_wifi_find_frequency(nm_device_get_platform(device),
nm_device_get_ifindex(device),
a_freqs);
else
freq = nm_platform_wifi_find_frequency(nm_device_get_platform(device),
nm_device_get_ifindex(device),
bg_freqs);
if (!freq)
freq = (g_strcmp0(band, "a") == 0) ? 5180 : 2462;
if (nm_wifi_ap_set_freq(ap, freq))
_ap_dump(self, LOGL_DEBUG, ap, "updated", 0);
}
static void
set_powersave(NMDevice *device)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMSettingWireless * s_wireless;
NMSettingWirelessPowersave val;
s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
g_return_if_fail(s_wireless);
val = nm_setting_wireless_get_powersave(s_wireless);
if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) {
val = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
"wifi.powersave",
device,
NM_SETTING_WIRELESS_POWERSAVE_IGNORE,
NM_SETTING_WIRELESS_POWERSAVE_ENABLE,
NM_SETTING_WIRELESS_POWERSAVE_IGNORE);
}
_LOGT(LOGD_WIFI, "powersave is set to %u", (unsigned) val);
if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE)
return;
nm_platform_wifi_set_powersave(nm_device_get_platform(device),
nm_device_get_ifindex(device),
val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE);
}
static NMActStageReturn
act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gs_unref_object NMSupplicantConfig *config = NULL;
NM80211Mode ap_mode;
NMActRequest * req;
NMWifiAP * ap;
NMConnection * connection;
const char * setting_name;
NMSettingWireless * s_wireless;
GError * error = NULL;
guint timeout;
NMActRequest * request;
NMActiveConnection * master_ac;
NMDevice * master;
nm_clear_g_source(&priv->sup_timeout_id);
nm_clear_g_source(&priv->link_timeout_id);
nm_clear_g_source(&priv->wps_timeout_id);
req = nm_device_get_act_request(device);
g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
ap = priv->current_ap;
if (!ap) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
goto out_fail;
}
ap_mode = nm_wifi_ap_get_mode(ap);
connection = nm_act_request_get_applied_connection(req);
s_wireless = nm_connection_get_setting_wireless(connection);
nm_assert(s_wireless);
/* If we need secrets, get them */
setting_name = nm_connection_need_secrets(connection, NULL);
if (setting_name) {
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) access point '%s' has security, but secrets are required.",
nm_connection_get_id(connection));
if (!handle_auth_or_fail(self, req, FALSE)) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
goto out_fail;
}
return NM_ACT_STAGE_RETURN_POSTPONE;
}
if (!wake_on_wlan_enable(self))
_LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN.");
/* have secrets, or no secrets required */
if (nm_connection_get_setting_wireless_security(connection)) {
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) connection '%s' has security, and secrets exist. No new secrets "
"needed.",
nm_connection_get_id(connection));
} else {
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) connection '%s' requires no security. No secrets needed.",
nm_connection_get_id(connection));
}
priv->ssid_found = FALSE;
/* Supplicant requires an initial frequency for Ad-Hoc, Hotspot and Mesh;
* if the user didn't specify one and we didn't find an AP that matched
* the connection, just pick a frequency the device supports.
*/
if (NM_IN_SET(ap_mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH) || nm_wifi_ap_is_hotspot(ap))
ensure_hotspot_frequency(self, s_wireless, ap);
if (ap_mode == NM_802_11_MODE_INFRA)
set_powersave(device);
/* Build up the supplicant configuration */
config = build_supplicant_config(self, connection, nm_wifi_ap_get_freq(ap), &error);
if (!config) {
_LOGE(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) couldn't build wireless configuration: %s",
error->message);
g_clear_error(&error);
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
goto out_fail;
}
/* Tell the supplicant in which bridge the interface is */
if ((request = nm_device_get_act_request(device))
&& (master_ac = nm_active_connection_get_master(NM_ACTIVE_CONNECTION(request)))
&& (master = nm_active_connection_get_device(master_ac))
&& nm_device_get_device_type(master) == NM_DEVICE_TYPE_BRIDGE) {
nm_supplicant_interface_set_bridge(priv->sup_iface, nm_device_get_iface(master));
} else
nm_supplicant_interface_set_bridge(priv->sup_iface, NULL);
nm_supplicant_interface_assoc(priv->sup_iface, config, supplicant_iface_assoc_cb, self);
/* Set up a timeout on the association attempt */
timeout = nm_device_get_supplicant_timeout(NM_DEVICE(self));
priv->sup_timeout_id = g_timeout_add_seconds(timeout, supplicant_connection_timeout_cb, self);
if (!priv->periodic_update_id)
priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self);
/* We'll get stage3 started when the supplicant connects */
return NM_ACT_STAGE_RETURN_POSTPONE;
out_fail:
cleanup_association_attempt(self, TRUE);
wake_on_wlan_restore(self);
return NM_ACT_STAGE_RETURN_FAILURE;
}
static NMActStageReturn
act_stage3_ip_config_start(NMDevice * device,
int addr_family,
gpointer * out_config,
NMDeviceStateReason *out_failure_reason)
{
gboolean indicate_addressing_running;
NMConnection *connection;
const char * method;
connection = nm_device_get_applied_connection(device);
method = nm_utils_get_ip_config_method(connection, addr_family);
if (addr_family == AF_INET)
indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
else {
indicate_addressing_running = NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NM_SETTING_IP6_CONFIG_METHOD_DHCP);
}
if (indicate_addressing_running)
nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
nm_device_get_ip_ifindex(device),
TRUE);
return NM_DEVICE_CLASS(nm_device_wifi_parent_class)
->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
}
static guint32
get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
{
return nm_device_get_configured_mtu_from_connection(device,
NM_TYPE_SETTING_WIRELESS,
out_source);
}
static gboolean
is_static_wep(NMConnection *connection)
{
NMSettingWirelessSecurity *s_wsec;
const char * str;
g_return_val_if_fail(connection != NULL, FALSE);
s_wsec = nm_connection_get_setting_wireless_security(connection);
if (!s_wsec)
return FALSE;
str = nm_setting_wireless_security_get_key_mgmt(s_wsec);
if (g_strcmp0(str, "none") != 0)
return FALSE;
str = nm_setting_wireless_security_get_auth_alg(s_wsec);
if (g_strcmp0(str, "leap") == 0)
return FALSE;
return TRUE;
}
static NMActStageReturn
act_stage4_ip_config_timeout(NMDevice * device,
int addr_family,
NMDeviceStateReason *out_failure_reason)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMConnection * connection;
NMSettingIPConfig * s_ip;
gboolean may_fail;
connection = nm_device_get_applied_connection(device);
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
may_fail = nm_setting_ip_config_get_may_fail(s_ip);
if (priv->mode == NM_802_11_MODE_AP)
goto call_parent;
if (may_fail || !is_static_wep(connection)) {
/* Not static WEP or failure allowed; let superclass handle it */
goto call_parent;
}
/* If IP configuration times out and it's a static WEP connection, that
* usually means the WEP key is wrong. WEP's Open System auth mode has
* no provision for figuring out if the WEP key is wrong, so you just have
* to wait for DHCP to fail to figure it out. For all other Wi-Fi security
* types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
* connection would have failed before IP configuration.
*
* Activation failed, we must have bad encryption key */
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"Activation: (wifi) could not get IP configuration for connection '%s'.",
nm_connection_get_id(connection));
if (!handle_auth_or_fail(self, NULL, TRUE)) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
return NM_ACT_STAGE_RETURN_FAILURE;
}
_LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets");
return NM_ACT_STAGE_RETURN_POSTPONE;
call_parent:
return NM_DEVICE_CLASS(nm_device_wifi_parent_class)
->act_stage4_ip_config_timeout(device, addr_family, out_failure_reason);
}
static void
activation_success_handler(NMDevice *device)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
int ifindex = nm_device_get_ifindex(device);
NMActRequest * req;
req = nm_device_get_act_request(device);
g_assert(req);
/* Clear any critical protocol notification in the wifi stack */
nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE);
/* There should always be a current AP, either a fake one because we haven't
* seen a scan result for the activated AP yet, or a real one from the
* supplicant's scan list.
*/
g_warn_if_fail(priv->current_ap);
if (priv->current_ap) {
if (nm_wifi_ap_get_fake(priv->current_ap)) {
gboolean ap_changed = FALSE;
gboolean update_bssid = !nm_wifi_ap_get_address(priv->current_ap);
gboolean update_rate = !nm_wifi_ap_get_max_bitrate(priv->current_ap);
NMEtherAddr bssid;
guint32 rate;
/* If the activation AP hasn't been seen by the supplicant in a scan
* yet, it will be "fake". This usually happens for Ad-Hoc and
* AP-mode connections. Fill in the details from the device itself
* until the supplicant sends the scan result.
*/
if (!nm_wifi_ap_get_freq(priv->current_ap))
ap_changed |= nm_wifi_ap_set_freq(
priv->current_ap,
nm_platform_wifi_get_frequency(nm_device_get_platform(device), ifindex));
if ((update_bssid || update_rate)
&& nm_platform_wifi_get_station(nm_device_get_platform(device),
ifindex,
update_bssid ? &bssid : NULL,
NULL,
update_rate ? &rate : NULL)) {
if (update_bssid && nm_ether_addr_is_valid(&bssid))
ap_changed |= nm_wifi_ap_set_address_bin(priv->current_ap, &bssid);
if (update_rate)
ap_changed |= nm_wifi_ap_set_max_bitrate(priv->current_ap, rate);
}
if (ap_changed)
_ap_dump(self, LOGL_DEBUG, priv->current_ap, "updated", 0);
}
nm_active_connection_set_specific_object(
NM_ACTIVE_CONNECTION(req),
nm_dbus_object_get_path(NM_DBUS_OBJECT(priv->current_ap)));
}
periodic_update(self);
update_seen_bssids_cache(self, priv->current_ap);
priv->scan_periodic_interval_sec = 0;
priv->scan_periodic_next_msec = 0;
}
static void
device_state_changed(NMDevice * device,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
gboolean clear_aps = FALSE;
if (new_state > NM_DEVICE_STATE_ACTIVATED)
wifi_secrets_cancel(self);
if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
/* Clean up the supplicant interface because in these states the
* device cannot be used.
*/
supplicant_interface_release(self);
nm_clear_g_source(&priv->periodic_update_id);
cleanup_association_attempt(self, TRUE);
cleanup_supplicant_failures(self);
remove_all_aps(self);
}
switch (new_state) {
case NM_DEVICE_STATE_UNMANAGED:
clear_aps = TRUE;
break;
case NM_DEVICE_STATE_UNAVAILABLE:
/* If the device is enabled and the supplicant manager is ready,
* acquire a supplicant interface and transition to DISCONNECTED because
* the device is now ready to use.
*/
if (priv->enabled && (nm_device_get_firmware_missing(device) == FALSE)) {
if (!priv->sup_iface)
supplicant_interface_acquire(self);
}
clear_aps = TRUE;
break;
case NM_DEVICE_STATE_NEED_AUTH:
if (priv->sup_iface)
nm_supplicant_interface_disconnect(priv->sup_iface);
break;
case NM_DEVICE_STATE_IP_CHECK:
/* Clear any critical protocol notification in the wifi stack */
nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
nm_device_get_ifindex(device),
FALSE);
break;
case NM_DEVICE_STATE_ACTIVATED:
activation_success_handler(device);
break;
case NM_DEVICE_STATE_FAILED:
/* Clear any critical protocol notification in the wifi stack */
nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
nm_device_get_ifindex(device),
FALSE);
break;
case NM_DEVICE_STATE_DISCONNECTED:
break;
default:
break;
}
if (clear_aps)
remove_all_aps(self);
_scan_notify_allowed(self, NM_TERNARY_DEFAULT);
}
static gboolean
get_enabled(NMDevice *device)
{
return NM_DEVICE_WIFI_GET_PRIVATE(device)->enabled;
}
static void
set_enabled(NMDevice *device, gboolean enabled)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
NMDeviceState state;
enabled = !!enabled;
if (priv->enabled == enabled)
return;
priv->enabled = enabled;
_LOGD(LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled");
state = nm_device_get_state(NM_DEVICE(self));
if (state < NM_DEVICE_STATE_UNAVAILABLE) {
_LOGD(LOGD_WIFI, "(%s): device blocked by UNMANAGED state", enabled ? "enable" : "disable");
return;
}
if (enabled) {
gboolean no_firmware = FALSE;
if (state != NM_DEVICE_STATE_UNAVAILABLE)
_LOGW(LOGD_CORE, "not in expected unavailable state!");
if (!nm_device_bring_up(NM_DEVICE(self), TRUE, &no_firmware)) {
_LOGD(LOGD_WIFI, "enable blocked by failure to bring device up");
if (no_firmware)
nm_device_set_firmware_missing(NM_DEVICE(device), TRUE);
else {
/* The device sucks, or the kernel was lying to us about the killswitch state */
priv->enabled = FALSE;
}
return;
}
/* Re-initialize the supplicant interface and wait for it to be ready */
cleanup_supplicant_failures(self);
supplicant_interface_release(self);
supplicant_interface_acquire(self);
_LOGD(LOGD_WIFI, "enable waiting on supplicant state");
} else {
nm_device_state_changed(NM_DEVICE(self),
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_NONE);
nm_device_take_down(NM_DEVICE(self), TRUE);
}
}
static gboolean
get_guessed_metered(NMDevice *device)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
return priv->current_ap && nm_wifi_ap_get_metered(priv->current_ap);
}
static gboolean
can_reapply_change(NMDevice * device,
const char *setting_name,
NMSetting * s_old,
NMSetting * s_new,
GHashTable *diffs,
GError ** error)
{
NMDeviceClass *device_class;
/* Only handle wireless setting here, delegate other settings to parent class */
if (nm_streq(setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) {
return nm_device_hash_check_invalid_keys(
diffs,
NM_SETTING_WIRELESS_SETTING_NAME,
error,
NM_SETTING_WIRELESS_SEEN_BSSIDS, /* ignored */
NM_SETTING_WIRELESS_MTU, /* reapplied with IP config */
NM_SETTING_WIRELESS_WAKE_ON_WLAN);
}
device_class = NM_DEVICE_CLASS(nm_device_wifi_parent_class);
return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
}
static void
reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new)
{
NMDeviceWifi *self = NM_DEVICE_WIFI(device);
NMDeviceState state = nm_device_get_state(device);
NM_DEVICE_CLASS(nm_device_wifi_parent_class)->reapply_connection(device, con_old, con_new);
_LOGD(LOGD_DEVICE, "reapplying wireless settings");
if (state >= NM_DEVICE_STATE_CONFIG && !wake_on_wlan_enable(self))
_LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN.");
}
/*****************************************************************************/
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(object);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
const char ** list;
switch (prop_id) {
case PROP_MODE:
g_value_set_uint(value, priv->mode);
break;
case PROP_BITRATE:
g_value_set_uint(value, priv->rate);
break;
case PROP_CAPABILITIES:
g_value_set_uint(value, priv->capabilities);
break;
case PROP_ACCESS_POINTS:
list = nm_wifi_aps_get_paths(&priv->aps_lst_head, TRUE);
g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list));
break;
case PROP_ACTIVE_ACCESS_POINT:
nm_dbus_utils_g_value_set_object_path(value, priv->current_ap);
break;
case PROP_SCANNING:
g_value_set_boolean(value, nm_device_wifi_get_scanning(self));
break;
case PROP_LAST_SCAN:
g_value_set_int64(
value,
priv->scan_last_complete_msec > 0
? nm_utils_monotonic_timestamp_as_boottime(priv->scan_last_complete_msec,
NM_UTILS_NSEC_PER_MSEC)
: (gint64) -1);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
NMDeviceWifi * device = NM_DEVICE_WIFI(object);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(device);
switch (prop_id) {
case PROP_CAPABILITIES:
/* construct-only */
priv->capabilities = g_value_get_uint(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_device_wifi_init(NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
c_list_init(&priv->aps_lst_head);
c_list_init(&priv->scanning_prohibited_lst_head);
c_list_init(&priv->scan_request_ssids_lst_head);
priv->aps_idx_by_supplicant_path = g_hash_table_new(nm_direct_hash, NULL);
priv->scan_last_request_started_at_msec = G_MININT64;
priv->hidden_probe_scan_warn = TRUE;
priv->mode = NM_802_11_MODE_INFRA;
priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
}
static void
constructed(GObject *object)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(object);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
G_OBJECT_CLASS(nm_device_wifi_parent_class)->constructed(object);
if (priv->capabilities & NM_WIFI_DEVICE_CAP_AP)
_LOGI(LOGD_PLATFORM | LOGD_WIFI, "driver supports Access Point (AP) mode");
/* Connect to the supplicant manager */
priv->sup_mgr = g_object_ref(nm_supplicant_manager_get());
}
NMDevice *
nm_device_wifi_new(const char *iface, NMDeviceWifiCapabilities capabilities)
{
return g_object_new(NM_TYPE_DEVICE_WIFI,
NM_DEVICE_IFACE,
iface,
NM_DEVICE_TYPE_DESC,
"802.11 Wi-Fi",
NM_DEVICE_DEVICE_TYPE,
NM_DEVICE_TYPE_WIFI,
NM_DEVICE_LINK_TYPE,
NM_LINK_TYPE_WIFI,
NM_DEVICE_RFKILL_TYPE,
RFKILL_TYPE_WLAN,
NM_DEVICE_WIFI_CAPABILITIES,
(guint) capabilities,
NULL);
}
static void
dispose(GObject *object)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(object);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_assert(c_list_is_empty(&priv->scanning_prohibited_lst_head));
nm_clear_g_source(&priv->periodic_update_id);
wifi_secrets_cancel(self);
cleanup_association_attempt(self, TRUE);
supplicant_interface_release(self);
cleanup_supplicant_failures(self);
g_clear_object(&priv->sup_mgr);
remove_all_aps(self);
if (priv->p2p_device) {
/* Destroy the P2P device. */
g_object_remove_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device);
nm_device_wifi_p2p_remove(g_steal_pointer(&priv->p2p_device));
}
G_OBJECT_CLASS(nm_device_wifi_parent_class)->dispose(object);
}
static void
finalize(GObject *object)
{
NMDeviceWifi * self = NM_DEVICE_WIFI(object);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
nm_assert(c_list_is_empty(&priv->aps_lst_head));
nm_assert(g_hash_table_size(priv->aps_idx_by_supplicant_path) == 0);
g_hash_table_unref(priv->aps_idx_by_supplicant_path);
G_OBJECT_CLASS(nm_device_wifi_parent_class)->finalize(object);
}
static void
nm_device_wifi_class_init(NMDeviceWifiClass *klass)
{
GObjectClass * object_class = G_OBJECT_CLASS(klass);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
NMDeviceClass * device_class = NM_DEVICE_CLASS(klass);
object_class->constructed = constructed;
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->dispose = dispose;
object_class->finalize = finalize;
dbus_object_class->interface_infos =
NM_DBUS_INTERFACE_INFOS(&nm_interface_info_device_wireless);
device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME;
device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME;
device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI);
device_class->can_auto_connect = can_auto_connect;
device_class->get_autoconnect_allowed = get_autoconnect_allowed;
device_class->is_available = is_available;
device_class->check_connection_compatible = check_connection_compatible;
device_class->check_connection_available = check_connection_available;
device_class->complete_connection = complete_connection;
device_class->get_enabled = get_enabled;
device_class->get_guessed_metered = get_guessed_metered;
device_class->set_enabled = set_enabled;
device_class->act_stage1_prepare = act_stage1_prepare;
device_class->act_stage2_config = act_stage2_config;
device_class->get_configured_mtu = get_configured_mtu;
device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
device_class->deactivate_async = deactivate_async;
device_class->deactivate = deactivate;
device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
device_class->unmanaged_on_quit = unmanaged_on_quit;
device_class->can_reapply_change = can_reapply_change;
device_class->reapply_connection = reapply_connection;
device_class->state_changed = device_state_changed;
obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_WIFI_MODE,
"",
"",
NM_802_11_MODE_UNKNOWN,
NM_802_11_MODE_AP,
NM_802_11_MODE_INFRA,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_BITRATE] = g_param_spec_uint(NM_DEVICE_WIFI_BITRATE,
"",
"",
0,
G_MAXUINT32,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ACCESS_POINTS] =
g_param_spec_boxed(NM_DEVICE_WIFI_ACCESS_POINTS,
"",
"",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ACTIVE_ACCESS_POINT] =
g_param_spec_string(NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CAPABILITIES] =
g_param_spec_uint(NM_DEVICE_WIFI_CAPABILITIES,
"",
"",
0,
G_MAXUINT32,
NM_WIFI_DEVICE_CAP_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_DEVICE_WIFI_SCANNING,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_LAST_SCAN] = g_param_spec_int64(NM_DEVICE_WIFI_LAST_SCAN,
"",
"",
-1,
G_MAXINT64,
-1,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
signals[P2P_DEVICE_CREATED] = g_signal_new(NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
NM_TYPE_DEVICE);
}