/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2005 - 2017 Red Hat, Inc. * Copyright (C) 2006 - 2008 Novell, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-device-wifi.h" #include #include #include #include "libnm-glib-aux/nm-ref-string.h" #include "libnm-glib-aux/nm-c-list.h" #include "nm-device-wifi-p2p.h" #include "nm-wifi-ap.h" #include "libnm-core-aux-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 "libnm-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 "libnm-core-intern/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 *hw_addr_scan = NULL; const char * generate_mac_address_mask; /* 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 > NM_IW_ESSID_MAX_SIZE) { 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_timeout_add_source(SCAN_EXTRA_DELAY_MSEC, _scan_request_delay_cb, self); 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_last_complete_msec + 200 > now_msec) { gint32 timeout_msec = priv->scan_last_complete_msec + 200 - now_msec; /* after a scan just completed, it is ratelimited for another 200 msec. This is in * addition to our rate limiting above (where scanning can take longer than our rate limit * duration). * * This gives the device a chance to autoconnect. Also, if a scanning just completed, * we want to back off a bit before starting again. */ _LOGT_scan("kickoff: don't scan (rate limited for another %d.%03d sec after previous scan)", timeout_msec / 1000, timeout_msec % 1000); nm_clear_g_source(&priv->scan_kickoff_timeout_id); priv->scan_kickoff_timeout_id = g_timeout_add(timeout_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_gbytes(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_gbytes(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_gbytes(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_gbytes(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_gbytes(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; NMActRequest * req; 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_gbytes(old_ssid)), new_bssid ?: "(none)", (new_ssid_s = _nm_utils_ssid_to_string_gbytes(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); req = nm_device_get_act_request(NM_DEVICE(self)); if (req) { nm_active_connection_set_specific_object( NM_ACTIVE_CONNECTION(req), new_ap ? nm_dbus_object_get_path(NM_DBUS_OBJECT(new_ap)) : NULL); } } } /* 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; _NMSettingWirelessWakeOnWLan wowl2; 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: wowl2 = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_CAST(wowl); if (wowl2 == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) { priv->wowlan_restore = wowl2; 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)), wowl2); } 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); }