mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2026-02-07 19:08:10 +01:00
Calling device_disassociated inside disconnect_cb was mostly pointless. Most attributes were already cleared by device_disconnect() when initiating the disconnection procedure. This patch also modifies the logic for triggering the autoconnect. If the user initiated the disconnect call, then autoconnect should not be triggered. If the disconnect was triggered by other means, then iwd will still enter autoconnect mode.
1258 lines
31 KiB
C
1258 lines
31 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 <ell/ell.h>
|
|
|
|
#include "src/iwd.h"
|
|
#include "src/common.h"
|
|
#include "src/util.h"
|
|
#include "src/ie.h"
|
|
#include "src/eapol.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"
|
|
|
|
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;
|
|
bool scanning;
|
|
uint32_t netdev_watch_id;
|
|
|
|
struct wiphy *wiphy;
|
|
struct netdev *netdev;
|
|
};
|
|
|
|
static struct l_queue *device_watches = NULL;
|
|
static uint32_t device_next_watch_id = 0;
|
|
|
|
static struct l_queue *device_list;
|
|
|
|
static void device_watchlist_item_free(void *userdata)
|
|
{
|
|
struct device_watchlist_item *item = userdata;
|
|
|
|
if (item->destroy)
|
|
item->destroy(item->userdata);
|
|
|
|
l_free(item);
|
|
}
|
|
|
|
static bool device_watchlist_item_match(const void *a, const void *b)
|
|
{
|
|
const struct device_watchlist_item *item = a;
|
|
uint32_t id = L_PTR_TO_UINT(b);
|
|
|
|
return item->id == id;
|
|
}
|
|
|
|
uint32_t device_watch_add(device_watch_func_t added,
|
|
device_watch_func_t removed,
|
|
void *userdata, device_destroy_func_t destroy)
|
|
{
|
|
struct device_watchlist_item *item;
|
|
|
|
item = l_new(struct device_watchlist_item, 1);
|
|
item->id = ++device_next_watch_id;
|
|
item->added = added;
|
|
item->removed = removed;
|
|
item->userdata = userdata;
|
|
item->destroy = destroy;
|
|
|
|
l_queue_push_tail(device_watches, item);
|
|
|
|
return item->id;
|
|
}
|
|
|
|
bool device_watch_remove(uint32_t id)
|
|
{
|
|
struct device_watchlist_item *item;
|
|
|
|
item = l_queue_remove_if(device_watches, device_watchlist_item_match,
|
|
L_UINT_TO_PTR(id));
|
|
if (!item)
|
|
return false;
|
|
|
|
device_watchlist_item_free(item);
|
|
return true;
|
|
}
|
|
|
|
void __device_watch_call_added(struct device *device)
|
|
{
|
|
const struct l_queue_entry *e;
|
|
|
|
for (e = l_queue_get_entries(device_watches); e; e = e->next) {
|
|
struct device_watchlist_item *item = e->data;
|
|
|
|
if (item->added)
|
|
item->added(device, item->userdata);
|
|
}
|
|
}
|
|
|
|
void __device_watch_call_removed(struct device *device)
|
|
{
|
|
const struct l_queue_entry *e;
|
|
|
|
for (e = l_queue_get_entries(device_watches); e; e = e->next) {
|
|
struct device_watchlist_item *item = e->data;
|
|
|
|
if (item->removed)
|
|
item->removed(device, item->userdata);
|
|
}
|
|
}
|
|
|
|
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 uint8_t *ssid, size_t ssid_len,
|
|
enum security security)
|
|
{
|
|
static char path[256];
|
|
unsigned int pos, i;
|
|
|
|
pos = snprintf(path, sizeof(path), "%s/", device_get_path(device));
|
|
|
|
for (i = 0; i < ssid_len && 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";
|
|
}
|
|
|
|
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;
|
|
enum security security;
|
|
const char *path;
|
|
double rankmod;
|
|
struct autoconnect_entry *entry;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If both an RSN and a WPA elements are present currently
|
|
* RSN takes priority and the WPA IE is ignored.
|
|
*/
|
|
if (bss->rsne) {
|
|
struct ie_rsn_info rsne;
|
|
int res = ie_parse_rsne_from_data(bss->rsne, bss->rsne[1] + 2,
|
|
&rsne);
|
|
if (res < 0) {
|
|
l_debug("Cannot parse RSN field (%d, %s)",
|
|
res, strerror(-res));
|
|
return;
|
|
}
|
|
|
|
security = scan_get_security(bss->capability, &rsne);
|
|
|
|
if (security == SECURITY_PSK)
|
|
bss->sha256 =
|
|
rsne.akm_suites & IE_RSN_AKM_SUITE_PSK_SHA256;
|
|
else if (security == SECURITY_8021X)
|
|
bss->sha256 =
|
|
rsne.akm_suites & IE_RSN_AKM_SUITE_8021X_SHA256;
|
|
} else if (bss->wpa) {
|
|
struct ie_rsn_info wpa;
|
|
int res = ie_parse_wpa_from_data(bss->wpa, bss->wpa[1] + 2,
|
|
&wpa);
|
|
if (res < 0) {
|
|
l_debug("Cannot parse WPA IE (%d, %s)",
|
|
res, strerror(-res));
|
|
return;
|
|
}
|
|
|
|
security = scan_get_security(bss->capability, &wpa);
|
|
} else
|
|
security = scan_get_security(bss->capability, NULL);
|
|
|
|
path = iwd_network_get_path(device, bss->ssid, bss->ssid_len,
|
|
security);
|
|
|
|
network = l_hashmap_lookup(device->networks, path);
|
|
if (!network) {
|
|
network = network_create(device, bss->ssid, bss->ssid_len,
|
|
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));
|
|
}
|
|
|
|
static bool new_scan_results(uint32_t wiphy_id, uint32_t ifindex,
|
|
struct l_queue *bss_list, void *userdata)
|
|
{
|
|
struct device *device = userdata;
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void device_disassociated(struct device *device)
|
|
{
|
|
struct network *network = device->connected_network;
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
|
|
l_debug("%d", device->index);
|
|
|
|
if (network) {
|
|
if (device->state == DEVICE_STATE_CONNECTED)
|
|
network_disconnected(network);
|
|
|
|
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");
|
|
}
|
|
|
|
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_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)
|
|
device_disassociated(device);
|
|
|
|
return;
|
|
}
|
|
|
|
network_connected(device->connected_network);
|
|
device_enter_state(device, DEVICE_STATE_CONNECTED);
|
|
}
|
|
|
|
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);
|
|
};
|
|
}
|
|
|
|
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 eapol_sm *sm = NULL;
|
|
|
|
if (security == SECURITY_PSK || security == SECURITY_8021X) {
|
|
uint16_t pairwise_ciphers, group_ciphers;
|
|
uint8_t rsne_buf[256];
|
|
struct ie_rsn_info info;
|
|
|
|
sm = eapol_sm_new();
|
|
|
|
eapol_sm_set_authenticator_address(sm, bss->addr);
|
|
eapol_sm_set_supplicant_address(sm,
|
|
netdev_get_address(device->netdev));
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
if (security == SECURITY_PSK)
|
|
info.akm_suites =
|
|
bss->sha256 ? IE_RSN_AKM_SUITE_PSK_SHA256 :
|
|
IE_RSN_AKM_SUITE_PSK;
|
|
else
|
|
info.akm_suites =
|
|
bss->sha256 ? IE_RSN_AKM_SUITE_8021X_SHA256 :
|
|
IE_RSN_AKM_SUITE_8021X;
|
|
|
|
bss_get_supported_ciphers(bss,
|
|
&pairwise_ciphers, &group_ciphers);
|
|
|
|
info.pairwise_ciphers = wiphy_select_cipher(wiphy,
|
|
pairwise_ciphers);
|
|
info.group_cipher = wiphy_select_cipher(wiphy, group_ciphers);
|
|
|
|
/* RSN takes priority */
|
|
if (bss->rsne) {
|
|
ie_build_rsne(&info, rsne_buf);
|
|
eapol_sm_set_ap_rsn(sm, bss->rsne, bss->rsne[1] + 2);
|
|
eapol_sm_set_own_rsn(sm, rsne_buf, rsne_buf[1] + 2);
|
|
} else {
|
|
ie_build_wpa(&info, rsne_buf);
|
|
eapol_sm_set_ap_wpa(sm, bss->wpa, bss->wpa[1] + 2);
|
|
eapol_sm_set_own_wpa(sm, rsne_buf, rsne_buf[1] + 2);
|
|
}
|
|
|
|
if (security == SECURITY_PSK)
|
|
eapol_sm_set_pmk(sm, network_get_psk(network));
|
|
else
|
|
eapol_sm_set_8021x_config(sm,
|
|
network_get_settings(network));
|
|
}
|
|
|
|
device->connect_pending = l_dbus_message_ref(message);
|
|
|
|
if (netdev_connect(device->netdev, bss, sm,
|
|
device_netdev_event,
|
|
device_connect_cb, device) < 0) {
|
|
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");
|
|
}
|
|
|
|
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;
|
|
bool trigger_autoconnect = true;
|
|
|
|
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);
|
|
|
|
/*
|
|
* If the disconnect was triggered by the user, don't
|
|
* autoconnect. Wait for user's explicit instructions to scan
|
|
* and connect to the network
|
|
*/
|
|
trigger_autoconnect = false;
|
|
}
|
|
|
|
device_enter_state(device, DEVICE_STATE_DISCONNECTED);
|
|
|
|
if (trigger_autoconnect)
|
|
device_enter_state(device, DEVICE_STATE_AUTOCONNECT);
|
|
}
|
|
|
|
int device_disconnect(struct device *device)
|
|
{
|
|
struct network *network = device->connected_network;
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
|
|
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.
|
|
*/
|
|
if (device->state == DEVICE_STATE_CONNECTED)
|
|
network_disconnected(network);
|
|
|
|
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");
|
|
|
|
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("");
|
|
|
|
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;
|
|
|
|
l_dbus_message_builder_append_basic(builder, 'b', &device->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;
|
|
}
|
|
|
|
l_dbus_message_builder_append_basic(builder, 's', statestr);
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
__device_watch_call_added(device);
|
|
|
|
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));
|
|
|
|
if (device->connected_network) {
|
|
struct network *network = device->connected_network;
|
|
|
|
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");
|
|
}
|
|
|
|
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();
|
|
|
|
__device_watch_call_removed(device);
|
|
|
|
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();
|
|
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;
|
|
|
|
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);
|
|
|
|
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)
|
|
__device_watch_call_removed(device);
|
|
|
|
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);
|
|
|
|
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, true))
|
|
return false;
|
|
|
|
device_watches = l_queue_new();
|
|
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;
|
|
|
|
l_queue_destroy(device_watches, device_watchlist_item_free);
|
|
|
|
l_dbus_unregister_interface(dbus_get_bus(), IWD_DEVICE_INTERFACE);
|
|
|
|
return true;
|
|
}
|