3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2026-02-08 12:07:59 +01:00
iwd/src/device.c
Andrew Zaborowski 95847189ab device: Start a roaming attempt on low RSSI detection
Trigger a roam attempt when the RSSI level has been low for at least 5
seconds using the netdev RSSI LOW/HIGH events.  See if neighbor reports
are supported and if so, request and process the neighbor reports list
to restrict the number of channels to be scanned.  The scanning part is
not included in this patch for readability.
2017-01-18 17:27:35 -06:00

1553 lines
39 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2013-2016 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <linux/if_ether.h>
#include <ell/ell.h>
#include "src/iwd.h"
#include "src/common.h"
#include "src/util.h"
#include "src/ie.h"
#include "src/handshake.h"
#include "src/wiphy.h"
#include "src/scan.h"
#include "src/netdev.h"
#include "src/dbus.h"
#include "src/network.h"
#include "src/device.h"
#include "src/watchlist.h"
struct device_watchlist_item {
uint32_t id;
device_watch_func_t added;
device_watch_func_t removed;
void *userdata;
device_destroy_func_t destroy;
};
struct autoconnect_entry {
uint16_t rank;
struct network *network;
struct scan_bss *bss;
};
struct device {
uint32_t index;
enum device_state state;
struct l_queue *bss_list;
struct l_queue *old_bss_list;
struct l_dbus_message *scan_pending;
struct l_hashmap *networks;
struct l_queue *networks_sorted;
struct scan_bss *connected_bss;
struct network *connected_network;
struct l_queue *autoconnect_list;
struct l_dbus_message *connect_pending;
struct l_dbus_message *disconnect_pending;
uint32_t netdev_watch_id;
struct watchlist state_watches;
struct l_timeout *roam_trigger_timeout;
struct wiphy *wiphy;
struct netdev *netdev;
bool scanning : 1;
bool autoconnect : 1;
bool preparing_roam : 1;
};
static struct watchlist device_watches;
static struct l_queue *device_list;
uint32_t device_watch_add(device_watch_func_t func,
void *userdata, device_destroy_func_t destroy)
{
return watchlist_add(&device_watches, func, userdata, destroy);
}
bool device_watch_remove(uint32_t id)
{
return watchlist_remove(&device_watches, id);
}
void __iwd_device_foreach(iwd_device_foreach_func func, void *user_data)
{
const struct l_queue_entry *device_entry;
for (device_entry = l_queue_get_entries(device_list); device_entry;
device_entry = device_entry->next) {
struct device *device = device_entry->data;
func(device, user_data);
}
}
static const char *iwd_network_get_path(struct device *device,
const char *ssid,
enum security security)
{
static char path[256];
unsigned int pos, i;
pos = snprintf(path, sizeof(path), "%s/", device_get_path(device));
for (i = 0; ssid[i] && pos < sizeof(path); i++)
pos += snprintf(path + pos, sizeof(path) - pos, "%02x",
ssid[i]);
snprintf(path + pos, sizeof(path) - pos, "_%s",
security_to_str(security));
return path;
}
static const char *device_state_to_string(enum device_state state)
{
switch (state) {
case DEVICE_STATE_OFF:
return "off";
case DEVICE_STATE_DISCONNECTED:
return "disconnected";
case DEVICE_STATE_AUTOCONNECT:
return "autoconnect";
case DEVICE_STATE_CONNECTING:
return "connecting";
case DEVICE_STATE_CONNECTED:
return "connected";
case DEVICE_STATE_DISCONNECTING:
return "disconnecting";
case DEVICE_STATE_ROAMING:
return "roaming";
}
return "invalid";
}
static void device_autoconnect_next(struct device *device)
{
struct autoconnect_entry *entry;
int r;
while ((entry = l_queue_pop_head(device->autoconnect_list))) {
l_debug("Considering autoconnecting to BSS '%s' with SSID: %s,"
" freq: %u, rank: %u, strength: %i",
util_address_to_string(entry->bss->addr),
network_get_ssid(entry->network),
entry->bss->frequency, entry->rank,
entry->bss->signal_strength);
/* TODO: Blacklist the network from auto-connect */
r = network_autoconnect(entry->network, entry->bss);
l_free(entry);
if (!r)
return;
}
}
static void bss_free(void *data)
{
struct scan_bss *bss = data;
const char *addr;
addr = util_address_to_string(bss->addr);
l_debug("Freeing BSS %s", addr);
scan_bss_free(bss);
}
static void network_free(void *data)
{
struct network *network = data;
network_remove(network, -ESHUTDOWN);
}
static int autoconnect_rank_compare(const void *a, const void *b, void *user)
{
const struct autoconnect_entry *new_ae = a;
const struct autoconnect_entry *ae = b;
return ae->rank - new_ae->rank;
}
static bool process_network(const void *key, void *data, void *user_data)
{
struct network *network = data;
struct device *device = user_data;
if (!network_bss_list_isempty(network)) {
/* Build the network list ordered by rank */
network_rank_update(network);
l_queue_insert(device->networks_sorted, network,
network_rank_compare, NULL);
return false;
}
/* Drop networks that have no more BSSs in range */
l_debug("No remaining BSSs for SSID: %s -- Removing network",
network_get_ssid(network));
network_remove(network, -ERANGE);
return true;
}
static void process_bss(struct device *device, struct scan_bss *bss,
struct timespec *timestamp)
{
struct network *network;
struct ie_rsn_info info;
int r;
enum security security;
const char *path;
double rankmod;
struct autoconnect_entry *entry;
char ssid[33];
l_debug("Found BSS '%s' with SSID: %s, freq: %u, rank: %u, "
"strength: %i",
util_address_to_string(bss->addr),
util_ssid_to_utf8(bss->ssid_len, bss->ssid),
bss->frequency, bss->rank, bss->signal_strength);
if (!util_ssid_is_utf8(bss->ssid_len, bss->ssid)) {
l_warn("Ignoring BSS with non-UTF8 SSID");
return;
}
memcpy(ssid, bss->ssid, bss->ssid_len);
ssid[bss->ssid_len] = '\0';
memset(&info, 0, sizeof(info));
r = scan_bss_get_rsn_info(bss, &info);
if (r < 0) {
if (r != -ENOENT)
return;
security = scan_get_security(bss->capability, NULL);
} else
security = scan_get_security(bss->capability, &info);
path = iwd_network_get_path(device, ssid, security);
network = l_hashmap_lookup(device->networks, path);
if (!network) {
network = network_create(device, ssid, security);
if (!network_register(network, path)) {
network_remove(network, -EINVAL);
return;
}
l_hashmap_insert(device->networks,
network_get_path(network), network);
l_debug("Added new Network \"%s\" security %s",
network_get_ssid(network), security_to_str(security));
}
if (network_bss_list_isempty(network))
network_seen(network, timestamp);
network_bss_add(network, bss);
if (device->state != DEVICE_STATE_AUTOCONNECT)
return;
/* See if network is autoconnectable (is a known network) */
if (!network_rankmod(network, &rankmod))
return;
entry = l_new(struct autoconnect_entry, 1);
entry->network = network;
entry->bss = bss;
entry->rank = bss->rank * rankmod;
l_queue_insert(device->autoconnect_list, entry,
autoconnect_rank_compare, NULL);
}
static bool bss_match(const void *a, const void *b)
{
const struct scan_bss *bss_a = a;
const struct scan_bss *bss_b = b;
return !memcmp(bss_a->addr, bss_b->addr, sizeof(bss_a->addr));
}
/*
* Used when scan results were obtained; either from passive scan running
* inside device.c or active scans running in other state machines, e.g. wsc.c
*/
void device_set_scan_results(struct device *device, struct l_queue *bss_list)
{
struct network *network;
const struct l_queue_entry *bss_entry;
struct timespec now;
struct l_dbus *dbus = dbus_get_bus();
clock_gettime(CLOCK_REALTIME, &now);
device->scanning = false;
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Scanning");
device->old_bss_list = device->bss_list;
device->bss_list = bss_list;
while ((network = l_queue_pop_head(device->networks_sorted)))
network_bss_list_clear(network);
l_queue_destroy(device->autoconnect_list, l_free);
device->autoconnect_list = l_queue_new();
for (bss_entry = l_queue_get_entries(bss_list); bss_entry;
bss_entry = bss_entry->next) {
struct scan_bss *bss = bss_entry->data;
process_bss(device, bss, &now);
}
if (device->connected_bss) {
struct scan_bss *bss;
bss = l_queue_find(device->bss_list, bss_match,
device->connected_bss);
if (!bss) {
l_warn("Connected BSS not in scan results!");
l_queue_push_tail(device->bss_list,
device->connected_bss);
network_bss_add(device->connected_network,
device->connected_bss);
l_queue_remove(device->old_bss_list,
device->connected_bss);
} else
device->connected_bss = bss;
}
l_hashmap_foreach_remove(device->networks, process_network, device);
l_queue_destroy(device->old_bss_list, bss_free);
device->old_bss_list = NULL;
if (device->state == DEVICE_STATE_AUTOCONNECT)
device_autoconnect_next(device);
}
static bool new_scan_results(uint32_t wiphy_id, uint32_t ifindex,
struct l_queue *bss_list, void *userdata)
{
struct device *device = userdata;
device_set_scan_results(device, bss_list);
return true;
}
struct network *device_get_connected_network(struct device *device)
{
return device->connected_network;
}
const char *device_get_path(struct device *device)
{
static char path[26];
snprintf(path, sizeof(path), "%s/%u", wiphy_get_path(device->wiphy),
device->index);
return path;
}
bool device_is_busy(struct device *device)
{
if (device->state != DEVICE_STATE_DISCONNECTED &&
device->state != DEVICE_STATE_AUTOCONNECT &&
device->state != DEVICE_STATE_OFF)
return true;
return false;
}
struct wiphy *device_get_wiphy(struct device *device)
{
return device->wiphy;
}
struct netdev *device_get_netdev(struct device *device)
{
return device->netdev;
}
uint32_t device_get_ifindex(struct device *device)
{
return device->index;
}
const uint8_t *device_get_address(struct device *device)
{
return netdev_get_address(device->netdev);
}
enum device_state device_get_state(struct device *device)
{
return device->state;
}
static void periodic_scan_trigger(int err, void *user_data)
{
struct device *device = user_data;
struct l_dbus *dbus = dbus_get_bus();
device->scanning = true;
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Scanning");
}
uint32_t device_add_state_watch(struct device *device,
device_state_watch_func_t func,
void *user_data,
device_destroy_func_t destroy)
{
return watchlist_add(&device->state_watches, func, user_data, destroy);
}
bool device_remove_state_watch(struct device *device, uint32_t id)
{
return watchlist_remove(&device->state_watches, id);
}
struct network *device_network_find(struct device *device, const char *ssid,
enum security security)
{
const char *path = iwd_network_get_path(device, ssid, security);
return l_hashmap_lookup(device->networks, path);
}
static void device_enter_state(struct device *device, enum device_state state)
{
struct l_dbus *dbus = dbus_get_bus();
bool disconnected;
l_debug("Old State: %s, new state: %s",
device_state_to_string(device->state),
device_state_to_string(state));
switch (state) {
case DEVICE_STATE_OFF:
scan_periodic_stop(device->index);
break;
case DEVICE_STATE_AUTOCONNECT:
scan_periodic_start(device->index, periodic_scan_trigger,
new_scan_results, device);
break;
case DEVICE_STATE_DISCONNECTED:
scan_periodic_stop(device->index);
break;
case DEVICE_STATE_CONNECTED:
scan_periodic_stop(device->index);
break;
case DEVICE_STATE_CONNECTING:
break;
case DEVICE_STATE_DISCONNECTING:
break;
case DEVICE_STATE_ROAMING:
break;
}
disconnected = device->state <= DEVICE_STATE_AUTOCONNECT;
if ((disconnected && state > DEVICE_STATE_AUTOCONNECT) ||
(!disconnected && state != device->state))
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "State");
device->state = state;
WATCHLIST_NOTIFY(&device->state_watches,
device_state_watch_func_t, state);
}
static void device_reset_connection_state(struct device *device)
{
struct network *network = device->connected_network;
struct l_dbus *dbus = dbus_get_bus();
if (!network)
return;
if (device->state == DEVICE_STATE_CONNECTED)
network_disconnected(network);
l_timeout_remove(device->roam_trigger_timeout);
device->roam_trigger_timeout = NULL;
device->preparing_roam = false;
device->connected_bss = NULL;
device->connected_network = NULL;
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "ConnectedNetwork");
l_dbus_property_changed(dbus, network_get_path(network),
IWD_NETWORK_INTERFACE, "Connected");
}
static void device_disassociated(struct device *device)
{
l_debug("%d", device->index);
device_reset_connection_state(device);
device_enter_state(device, DEVICE_STATE_DISCONNECTED);
if (device->autoconnect)
device_enter_state(device, DEVICE_STATE_AUTOCONNECT);
}
static void device_lost_beacon(struct device *device)
{
l_debug("%d", device->index);
if (device->connect_pending)
dbus_pending_reply(&device->connect_pending,
dbus_error_failed(device->connect_pending));
device_disassociated(device);
}
static void device_disconnect_by_ap(struct device *device)
{
l_debug("%d", device->index);
if (device->connect_pending) {
struct network *network = device->connected_network;
dbus_pending_reply(&device->connect_pending,
dbus_error_failed(device->connect_pending));
network_connect_failed(network);
}
device_disassociated(device);
}
static void device_roam_scan(struct device *device,
struct scan_freq_set *freq_set)
{
}
static void device_neighbor_report_cb(struct netdev *netdev,
const uint8_t *reports,
size_t reports_len, void *user_data)
{
struct device *device = user_data;
struct ie_tlv_iter iter;
int count_md = 0, count_no_md = 0;
struct scan_freq_set *freq_set_md, *freq_set_no_md;
uint32_t current_freq = 0;
struct handshake_state *hs = netdev_get_handshake(device->netdev);
/*
* Check if we're still attempting to roam -- if dbus Disconnect
* had been called in the meantime we just abort the attempt.
*/
if (!device->preparing_roam)
return;
if (!reports) {
/* Have to do a full scan */
device_roam_scan(device, NULL);
return;
}
freq_set_md = scan_freq_set_new();
freq_set_no_md = scan_freq_set_new();
ie_tlv_iter_init(&iter, reports, reports_len);
/* First see if any of the reports contain the MD bit set */
while (ie_tlv_iter_next(&iter)) {
struct ie_neighbor_report_info info;
enum scan_band band;
uint32_t freq;
const uint8_t *cc = NULL;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_NEIGHBOR_REPORT)
continue;
if (ie_parse_neighbor_report(&iter, &info) < 0)
continue;
l_debug("Neighbor report received for %s: ch %i "
"(oper class %i), %s",
util_address_to_string(info.addr),
(int) info.channel_num, (int) info.oper_class,
info.md ? "MD set" : "MD not set");
if (device->connected_bss->cc_present)
cc = device->connected_bss->cc;
band = scan_oper_class_to_band(cc, info.oper_class);
if (!band) {
l_debug("Ignored: unsupported oper class");
continue;
}
freq = scan_channel_to_freq(info.channel_num, band);
if (!freq) {
l_debug("Ignored: unsupported channel");
continue;
}
if (!memcmp(info.addr, device->connected_bss->addr, ETH_ALEN)) {
/*
* If this report is for the current AP, don't add
* it to any of the lists yet. We will need to scan
* its channel because it may still be the best ranked
* or the only visible AP.
*/
current_freq = freq;
continue;
}
/* Add the frequency to one of the lists */
if (info.md && hs->mde) {
scan_freq_set_add(freq_set_md, freq);
count_md += 1;
} else {
scan_freq_set_add(freq_set_no_md, freq);
count_no_md += 1;
}
}
if (!current_freq)
current_freq = device->connected_bss->frequency;
/*
* If there are neighbor reports with the MD bit set then the bit
* is probably valid so scan only the frequencies of the neighbors
* with that bit set, which will allow us to use Fast Transition.
* Some APs, such as those based on hostapd do not set the MD bit
* even if the neighbor is within the MD.
*
* In any case we only select the frequencies here and will check
* the IEs in the scan results as the authoritative information
* on whether we can use Fast Transition, and rank BSSes based on
* that.
*
* TODO: possibly save the neighbors from outside the MD and if
* none of the ones in the MD end up working, try a non-FT
* transition to those neighbors. We should be using a
* blacklisting mechanism (for both initial connection and
* transitions) so that cound_md would not count the
* BSSes already used and when it goes down to 0 we'd
* automatically fall back to the non-FT candidates and then to
* full scan.
*/
if (count_md) {
scan_freq_set_add(freq_set_md, current_freq);
device_roam_scan(device, freq_set_md);
} else if (count_no_md) {
scan_freq_set_add(freq_set_no_md, current_freq);
device_roam_scan(device, freq_set_no_md);
} else
device_roam_scan(device, NULL);
scan_freq_set_free(freq_set_md);
scan_freq_set_free(freq_set_no_md);
}
static void device_roam_trigger_cb(struct l_timeout *timeout, void *user_data)
{
struct device *device = user_data;
l_debug("%d", device->index);
l_timeout_remove(device->roam_trigger_timeout);
device->roam_trigger_timeout = NULL;
device->preparing_roam = true;
/*
* If current BSS supports Neighbor Reports, narrow the scan down
* to channels occupied by known neighbors in the ESS. This isn't
* 100% reliable as the neighbor lists are not required to be
* complete or current. It is likely still better than doing a
* full scan. 10.11.10.1: "A neighbor report may not be exhaustive
* either by choice, or due to the fact that there may be neighbor
* APs not known to the AP."
*/
if (device->connected_bss->cap_rm_neighbor_report)
if (netdev_neighbor_report_req(device->netdev,
device_neighbor_report_cb) == 0)
return;
/* Otherwise do a full scan for target BSS candidates */
device_roam_scan(device, NULL);
}
static void device_connect_cb(struct netdev *netdev, enum netdev_result result,
void *user_data)
{
struct device *device = user_data;
l_debug("%d, result: %d", device->index, result);
if (device->connect_pending) {
struct l_dbus_message *reply;
switch (result) {
case NETDEV_RESULT_ABORTED:
reply = dbus_error_aborted(device->connect_pending);
break;
case NETDEV_RESULT_OK:
reply = l_dbus_message_new_method_return(
device->connect_pending);
l_dbus_message_set_arguments(reply, "");
break;
default:
reply = dbus_error_failed(device->connect_pending);
break;
}
dbus_pending_reply(&device->connect_pending, reply);
}
if (result != NETDEV_RESULT_OK) {
if (result != NETDEV_RESULT_ABORTED) {
network_connect_failed(device->connected_network);
device_disassociated(device);
}
return;
}
network_connected(device->connected_network);
device_enter_state(device, DEVICE_STATE_CONNECTED);
device->autoconnect = true;
}
static void device_netdev_event(struct netdev *netdev, enum netdev_event event,
void *user_data)
{
struct device *device = user_data;
struct network *network = device->connected_network;
switch (event) {
case NETDEV_EVENT_AUTHENTICATING:
l_debug("Authenticating");
break;
case NETDEV_EVENT_ASSOCIATING:
l_debug("Associating");
break;
case NETDEV_EVENT_4WAY_HANDSHAKE:
l_debug("Handshaking");
break;
case NETDEV_EVENT_SETTING_KEYS:
l_debug("Setting keys");
/* If we got here, then our PSK works. Save if required */
network_sync_psk(network);
break;
case NETDEV_EVENT_LOST_BEACON:
device_lost_beacon(device);
break;
case NETDEV_EVENT_DISCONNECT_BY_AP:
device_disconnect_by_ap(device);
break;
case NETDEV_EVENT_DISCONNECT_BY_SME:
device_disassociated(device);
break;
case NETDEV_EVENT_RSSI_THRESHOLD_LOW:
if (device->roam_trigger_timeout ||
device->preparing_roam ||
device->state == DEVICE_STATE_ROAMING)
break;
/* Set a 5-second timeout */
device->roam_trigger_timeout =
l_timeout_create(5, device_roam_trigger_cb,
device, NULL);
break;
case NETDEV_EVENT_RSSI_THRESHOLD_HIGH:
l_timeout_remove(device->roam_trigger_timeout);
device->roam_trigger_timeout = NULL;
break;
};
}
bool device_set_autoconnect(struct device *device, bool autoconnect)
{
if (device->autoconnect == autoconnect)
return true;
device->autoconnect = autoconnect;
if (device->state == DEVICE_STATE_DISCONNECTED && autoconnect)
device_enter_state(device, DEVICE_STATE_AUTOCONNECT);
if (device->state == DEVICE_STATE_AUTOCONNECT && !autoconnect)
device_enter_state(device, DEVICE_STATE_DISCONNECTED);
return true;
}
static enum ie_rsn_akm_suite device_select_akm_suite(struct network *network,
struct scan_bss *bss,
struct ie_rsn_info *info)
{
enum security security = network_get_security(network);
/*
* If FT is available, use FT authentication to keep the door open
* for fast transitions. Otherwise use SHA256 version if present.
*/
if (security == SECURITY_8021X) {
if ((info->akm_suites & IE_RSN_AKM_SUITE_FT_OVER_8021X) &&
bss->rsne && bss->mde_present)
return IE_RSN_AKM_SUITE_FT_OVER_8021X;
if (info->akm_suites & IE_RSN_AKM_SUITE_8021X_SHA256)
return IE_RSN_AKM_SUITE_8021X_SHA256;
if (info->akm_suites & IE_RSN_AKM_SUITE_8021X)
return IE_RSN_AKM_SUITE_8021X;
} else if (security == SECURITY_PSK) {
if ((info->akm_suites & IE_RSN_AKM_SUITE_FT_USING_PSK) &&
bss->rsne && bss->mde_present)
return IE_RSN_AKM_SUITE_FT_USING_PSK;
if (info->akm_suites & IE_RSN_AKM_SUITE_PSK_SHA256)
return IE_RSN_AKM_SUITE_PSK_SHA256;
if (info->akm_suites & IE_RSN_AKM_SUITE_PSK)
return IE_RSN_AKM_SUITE_PSK;
}
return 0;
}
void device_connect_network(struct device *device, struct network *network,
struct scan_bss *bss,
struct l_dbus_message *message)
{
enum security security = network_get_security(network);
struct wiphy *wiphy = device->wiphy;
struct l_dbus *dbus = dbus_get_bus();
struct handshake_state *hs;
bool add_mde = false;
uint8_t *mde;
hs = handshake_state_new(netdev_get_ifindex(device->netdev));
if (security == SECURITY_PSK || security == SECURITY_8021X) {
const struct l_settings *settings = iwd_get_config();
struct ie_rsn_info bss_info;
uint8_t rsne_buf[256];
struct ie_rsn_info info;
const char *ssid;
uint32_t mfp_setting;
memset(&info, 0, sizeof(info));
memset(&bss_info, 0, sizeof(bss_info));
scan_bss_get_rsn_info(bss, &bss_info);
info.akm_suites = device_select_akm_suite(network, bss,
&bss_info);
if (!info.akm_suites)
goto not_supported;
info.pairwise_ciphers = wiphy_select_cipher(wiphy,
bss_info.pairwise_ciphers);
info.group_cipher = wiphy_select_cipher(wiphy,
bss_info.group_cipher);
if (!info.pairwise_ciphers || !info.group_cipher)
goto not_supported;
if (!l_settings_get_uint(settings, "General",
"ManagementFrameProtection", &mfp_setting))
mfp_setting = 1;
if (mfp_setting > 2) {
l_error("Invalid MFP value, using default of 1");
mfp_setting = 1;
}
switch (mfp_setting) {
case 0:
break;
case 1:
info.group_management_cipher =
wiphy_select_cipher(wiphy,
bss_info.group_management_cipher);
info.mfpc = info.group_management_cipher != 0;
break;
case 2:
info.group_management_cipher =
wiphy_select_cipher(wiphy,
bss_info.group_management_cipher);
/*
* MFP required on our side, but AP doesn't support MFP
* or cipher mismatch
*/
if (info.group_management_cipher == 0)
goto not_supported;
info.mfpc = true;
info.mfpr = true;
break;
}
if (bss_info.mfpr && !info.mfpc)
goto not_supported;
ssid = network_get_ssid(network);
handshake_state_set_ssid(hs, (void *) ssid, strlen(ssid));
/* RSN takes priority */
if (bss->rsne) {
ie_build_rsne(&info, rsne_buf);
handshake_state_set_ap_rsn(hs, bss->rsne);
handshake_state_set_own_rsn(hs, rsne_buf);
} else {
ie_build_wpa(&info, rsne_buf);
handshake_state_set_ap_wpa(hs, bss->wpa);
handshake_state_set_own_wpa(hs, rsne_buf);
}
if (security == SECURITY_PSK)
handshake_state_set_pmk(hs, network_get_psk(network));
else
handshake_state_set_8021x_config(hs,
network_get_settings(network));
if (info.akm_suites & (IE_RSN_AKM_SUITE_FT_OVER_8021X |
IE_RSN_AKM_SUITE_FT_USING_PSK |
IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256))
add_mde = true;
}
if (security == SECURITY_NONE)
/* Perform FT association if available */
add_mde = bss->mde_present;
if (add_mde) {
mde = l_malloc(5);
/* The MDE advertised by the BSS must be passed verbatim */
mde[0] = IE_TYPE_MOBILITY_DOMAIN;
mde[1] = 3;
memcpy(mde + 2, bss->mde, 3);
handshake_state_set_mde(hs, mde);
} else
mde = NULL;
device->connect_pending = l_dbus_message_ref(message);
if (netdev_connect(device->netdev, bss, hs, device_netdev_event,
device_connect_cb, device) < 0) {
handshake_state_free(hs);
l_free(mde);
dbus_pending_reply(&device->connect_pending,
dbus_error_failed(device->connect_pending));
return;
}
device->connected_bss = bss;
device->connected_network = network;
device_enter_state(device, DEVICE_STATE_CONNECTING);
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "ConnectedNetwork");
l_dbus_property_changed(dbus, network_get_path(network),
IWD_NETWORK_INTERFACE, "Connected");
return;
not_supported:
l_dbus_send(dbus, dbus_error_not_supported(message));
}
static void device_scan_triggered(int err, void *user_data)
{
struct device *device = user_data;
struct l_dbus_message *reply;
struct l_dbus *dbus = dbus_get_bus();
l_debug("device_scan_triggered: %i", err);
if (err < 0) {
dbus_pending_reply(&device->scan_pending,
dbus_error_failed(device->scan_pending));
return;
}
l_debug("Scan triggered for %s", netdev_get_name(device->netdev));
device->scanning = true;
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Scanning");
reply = l_dbus_message_new_method_return(device->scan_pending);
l_dbus_message_set_arguments(reply, "");
dbus_pending_reply(&device->scan_pending, reply);
}
static struct l_dbus_message *device_scan(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct device *device = user_data;
l_debug("Scan called from DBus");
if (device->scan_pending)
return dbus_error_busy(message);
device->scan_pending = l_dbus_message_ref(message);
if (!scan_passive(device->index, device_scan_triggered,
new_scan_results, device, NULL))
return dbus_error_failed(message);
return NULL;
}
static void device_disconnect_cb(struct netdev *netdev, bool success,
void *user_data)
{
struct device *device = user_data;
l_debug("%d, success: %d", device->index, success);
if (device->disconnect_pending) {
struct l_dbus_message *reply;
if (success) {
reply = l_dbus_message_new_method_return(
device->disconnect_pending);
l_dbus_message_set_arguments(reply, "");
} else
reply = dbus_error_failed(device->disconnect_pending);
dbus_pending_reply(&device->disconnect_pending, reply);
}
device_enter_state(device, DEVICE_STATE_DISCONNECTED);
if (device->autoconnect)
device_enter_state(device, DEVICE_STATE_AUTOCONNECT);
}
int device_disconnect(struct device *device)
{
if (device->state == DEVICE_STATE_DISCONNECTING)
return -EBUSY;
if (!device->connected_bss)
return -ENOTCONN;
if (netdev_disconnect(device->netdev, device_disconnect_cb, device) < 0)
return -EIO;
/*
* If the disconnect somehow fails we won't know if we're still
* connected so we may as well indicate now that we're no longer
* connected.
*/
device_reset_connection_state(device);
device_enter_state(device, DEVICE_STATE_DISCONNECTING);
return 0;
}
static struct l_dbus_message *device_dbus_disconnect(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct device *device = user_data;
int result;
l_debug("");
/*
* Disconnect was triggered by the user, don't autoconnect. Wait for
* the user's explicit instructions to scan and connect to the network
*/
device_set_autoconnect(device, false);
result = device_disconnect(device);
if (result == -EBUSY)
return dbus_error_busy(message);
if (result == -ENOTCONN)
return dbus_error_not_connected(message);
if (result < 0)
return dbus_error_failed(message);
device->disconnect_pending = l_dbus_message_ref(message);
return NULL;
}
static struct l_dbus_message *device_get_networks(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct device *device = user_data;
struct l_dbus_message *reply;
struct l_dbus_message_builder *builder;
const struct l_queue_entry *entry;
reply = l_dbus_message_new_method_return(message);
builder = l_dbus_message_builder_new(reply);
l_dbus_message_builder_enter_array(builder, "(osns)");
for (entry = l_queue_get_entries(device->networks_sorted); entry;
entry = entry->next) {
const struct network *network = entry->data;
enum security security = network_get_security(network);
int32_t signal_strength = network_get_signal_strength(network);
l_dbus_message_builder_enter_struct(builder, "osns");
l_dbus_message_builder_append_basic(builder, 'o',
network_get_path(network));
l_dbus_message_builder_append_basic(builder, 's',
network_get_ssid(network));
l_dbus_message_builder_append_basic(builder, 'n',
&signal_strength);
l_dbus_message_builder_append_basic(builder, 's',
security_to_str(security));
l_dbus_message_builder_leave_struct(builder);
}
l_dbus_message_builder_leave_array(builder);
l_dbus_message_builder_finalize(builder);
l_dbus_message_builder_destroy(builder);
return reply;
}
static bool device_property_get_name(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
l_dbus_message_builder_append_basic(builder, 's',
netdev_get_name(device->netdev));
return true;
}
static bool device_property_get_address(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
const char *str;
str = util_address_to_string(netdev_get_address(device->netdev));
l_dbus_message_builder_append_basic(builder, 's', str);
return true;
}
static bool device_property_get_connected_network(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
if (!device->connected_network)
return false;
l_dbus_message_builder_append_basic(builder, 'o',
network_get_path(device->connected_network));
return true;
}
static bool device_property_get_powered(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
bool powered = device->state != DEVICE_STATE_OFF;
l_dbus_message_builder_append_basic(builder, 'b', &powered);
return true;
}
struct set_powered_cb_data {
struct device *device;
struct l_dbus *dbus;
struct l_dbus_message *message;
l_dbus_property_complete_cb_t complete;
};
static void set_powered_cb(struct netdev *netdev, int result, void *user_data)
{
struct set_powered_cb_data *cb_data = user_data;
struct l_dbus_message *reply = NULL;
if (result < 0)
reply = dbus_error_failed(cb_data->message);
cb_data->complete(cb_data->dbus, cb_data->message, reply);
}
static struct l_dbus_message *device_property_set_powered(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_iter *new_value,
l_dbus_property_complete_cb_t complete,
void *user_data)
{
struct device *device = user_data;
bool powered;
struct set_powered_cb_data *cb_data;
if (!l_dbus_message_iter_get_variant(new_value, "b", &powered))
return dbus_error_invalid_args(message);
if (powered == (device->state != DEVICE_STATE_OFF)) {
complete(dbus, message, NULL);
return NULL;
}
cb_data = l_new(struct set_powered_cb_data, 1);
cb_data->device = device;
cb_data->dbus = dbus;
cb_data->message = message;
cb_data->complete = complete;
netdev_set_powered(device->netdev, powered, set_powered_cb, cb_data,
l_free);
return NULL;
}
static bool device_property_get_scanning(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
bool scanning = device->scanning;
l_dbus_message_builder_append_basic(builder, 'b', &scanning);
return true;
}
static bool device_property_get_state(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
const char *statestr = "unknown";
switch (device->state) {
case DEVICE_STATE_CONNECTED:
statestr = "connected";
break;
case DEVICE_STATE_CONNECTING:
statestr = "connecting";
break;
case DEVICE_STATE_DISCONNECTING:
statestr = "disconnecting";
break;
case DEVICE_STATE_OFF:
case DEVICE_STATE_DISCONNECTED:
case DEVICE_STATE_AUTOCONNECT:
statestr = "disconnected";
break;
case DEVICE_STATE_ROAMING:
statestr = "roaming";
break;
}
l_dbus_message_builder_append_basic(builder, 's', statestr);
return true;
}
static bool device_property_get_adapter(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct device *device = user_data;
l_dbus_message_builder_append_basic(builder, 'o',
wiphy_get_path(device->wiphy));
return true;
}
static void setup_device_interface(struct l_dbus_interface *interface)
{
l_dbus_interface_method(interface, "Scan", 0,
device_scan, "", "");
l_dbus_interface_method(interface, "Disconnect", 0,
device_dbus_disconnect, "", "");
l_dbus_interface_method(interface, "GetOrderedNetworks", 0,
device_get_networks, "a(osns)", "",
"networks");
l_dbus_interface_property(interface, "Name", 0, "s",
device_property_get_name, NULL);
l_dbus_interface_property(interface, "Address", 0, "s",
device_property_get_address, NULL);
l_dbus_interface_property(interface, "ConnectedNetwork", 0, "o",
device_property_get_connected_network,
NULL);
l_dbus_interface_property(interface, "Powered", 0, "b",
device_property_get_powered,
device_property_set_powered);
l_dbus_interface_property(interface, "Scanning", 0, "b",
device_property_get_scanning, NULL);
l_dbus_interface_property(interface, "State", 0, "s",
device_property_get_state, NULL);
l_dbus_interface_property(interface, "Adapter", 0, "o",
device_property_get_adapter, NULL);
}
static bool device_remove_network(const void *key, void *data, void *user_data)
{
struct network *network = data;
network_remove(network, -ESHUTDOWN);
return true;
}
static void device_netdev_notify(struct netdev *netdev,
enum netdev_watch_event event,
void *user_data)
{
struct device *device = user_data;
struct l_dbus *dbus = dbus_get_bus();
switch (event) {
case NETDEV_WATCH_EVENT_UP:
device_enter_state(device, DEVICE_STATE_AUTOCONNECT);
WATCHLIST_NOTIFY(&device_watches, device_watch_func_t,
device, DEVICE_EVENT_INSERTED);
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Powered");
break;
case NETDEV_WATCH_EVENT_DOWN:
device_enter_state(device, DEVICE_STATE_OFF);
if (device->scan_pending)
dbus_pending_reply(&device->scan_pending,
dbus_error_aborted(device->scan_pending));
if (device->connect_pending)
dbus_pending_reply(&device->connect_pending,
dbus_error_aborted(device->connect_pending));
device_reset_connection_state(device);
l_hashmap_foreach_remove(device->networks,
device_remove_network, device);
l_queue_destroy(device->autoconnect_list, l_free);
device->autoconnect_list = l_queue_new();
l_queue_destroy(device->bss_list, bss_free);
device->bss_list = l_queue_new();
l_queue_destroy(device->networks_sorted, NULL);
device->networks_sorted = l_queue_new();
WATCHLIST_NOTIFY(&device_watches, device_watch_func_t,
device, DEVICE_EVENT_REMOVED);
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Powered");
break;
case NETDEV_WATCH_EVENT_NAME_CHANGE:
l_dbus_property_changed(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, "Name");
break;
}
}
struct device *device_create(struct wiphy *wiphy, struct netdev *netdev)
{
struct device *device;
struct l_dbus *dbus = dbus_get_bus();
uint32_t ifindex = netdev_get_ifindex(netdev);
device = l_new(struct device, 1);
device->bss_list = l_queue_new();
device->networks = l_hashmap_new();
watchlist_init(&device->state_watches);
l_hashmap_set_hash_function(device->networks, l_str_hash);
l_hashmap_set_compare_function(device->networks,
(l_hashmap_compare_func_t) strcmp);
device->networks_sorted = l_queue_new();
device->index = ifindex;
device->wiphy = wiphy;
device->netdev = netdev;
device->autoconnect = true;
l_queue_push_head(device_list, device);
if (!l_dbus_object_add_interface(dbus, device_get_path(device),
IWD_DEVICE_INTERFACE, device))
l_info("Unable to register %s interface", IWD_DEVICE_INTERFACE);
if (!l_dbus_object_add_interface(dbus, device_get_path(device),
L_DBUS_INTERFACE_PROPERTIES, device))
l_info("Unable to register %s interface",
L_DBUS_INTERFACE_PROPERTIES);
scan_ifindex_add(device->index);
device_netdev_notify(netdev, netdev_get_is_up(netdev) ?
NETDEV_WATCH_EVENT_UP :
NETDEV_WATCH_EVENT_DOWN,
device);
device->netdev_watch_id =
netdev_watch_add(netdev, device_netdev_notify, device);
return device;
}
static void device_free(void *user)
{
struct device *device = user;
struct l_dbus *dbus;
l_debug("");
if (device->scan_pending)
dbus_pending_reply(&device->scan_pending,
dbus_error_aborted(device->scan_pending));
if (device->connect_pending)
dbus_pending_reply(&device->connect_pending,
dbus_error_aborted(device->connect_pending));
if (device->state != DEVICE_STATE_OFF)
WATCHLIST_NOTIFY(&device_watches, device_watch_func_t,
device, DEVICE_EVENT_REMOVED);
watchlist_destroy(&device->state_watches);
dbus = dbus_get_bus();
l_dbus_unregister_object(dbus, device_get_path(device));
l_queue_destroy(device->networks_sorted, NULL);
l_hashmap_destroy(device->networks, network_free);
l_queue_destroy(device->bss_list, bss_free);
l_queue_destroy(device->old_bss_list, bss_free);
l_queue_destroy(device->autoconnect_list, l_free);
netdev_watch_remove(device->netdev, device->netdev_watch_id);
l_timeout_remove(device->roam_trigger_timeout);
scan_ifindex_remove(device->index);
l_free(device);
}
void device_remove(struct device *device)
{
if (!l_queue_remove(device_list, device))
return;
device_free(device);
}
bool device_init(void)
{
if (!l_dbus_register_interface(dbus_get_bus(),
IWD_DEVICE_INTERFACE,
setup_device_interface,
NULL, false))
return false;
watchlist_init(&device_watches);
device_list = l_queue_new();
return true;
}
bool device_exit(void)
{
if (!l_queue_isempty(device_list))
l_warn("device_list isn't empty!");
l_queue_destroy(device_list, device_free);
device_list = NULL;
watchlist_destroy(&device_watches);
l_dbus_unregister_interface(dbus_get_bus(), IWD_DEVICE_INTERFACE);
return true;
}