iwd/src/netdev.c

6097 lines
149 KiB
C
Raw Normal View History

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2013-2019 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 <stdlib.h>
#include <alloca.h>
2018-08-20 06:12:14 +02:00
#include <stdio.h>
#include <linux/rtnetlink.h>
#include <net/if_arp.h>
#include <linux/if.h>
2016-06-16 22:23:45 +02:00
#include <linux/if_packet.h>
2016-05-31 19:57:24 +02:00
#include <linux/if_ether.h>
#include <arpa/inet.h>
#include <linux/filter.h>
2016-06-16 22:23:45 +02:00
#include <sys/socket.h>
#include <errno.h>
#include <ell/ell.h>
#include "ell/useful.h"
#include "linux/nl80211.h"
#include "src/iwd.h"
#include "src/module.h"
#include "src/wiphy.h"
#include "src/ie.h"
#include "src/mpdu.h"
#include "src/eapol.h"
#include "src/handshake.h"
#include "src/crypto.h"
#include "src/scan.h"
#include "src/netdev.h"
#include "src/ft.h"
#include "src/util.h"
#include "src/watchlist.h"
#include "src/sae.h"
#include "src/nl80211util.h"
#include "src/nl80211cmd.h"
#include "src/owe.h"
#include "src/fils.h"
#include "src/auth-proto.h"
2020-01-16 03:40:59 +01:00
#include "src/frame-xchg.h"
#include "src/diagnostic.h"
#ifndef ENOTSUPP
#define ENOTSUPP 524
#endif
enum connection_type {
CONNECTION_TYPE_SOFTMAC,
CONNECTION_TYPE_FULLMAC,
CONNECTION_TYPE_SAE_OFFLOAD,
CONNECTION_TYPE_PSK_OFFLOAD,
CONNECTION_TYPE_8021X_OFFLOAD,
};
2019-05-18 00:05:52 +02:00
static uint32_t unicast_watch;
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state {
struct handshake_state super;
uint32_t pairwise_new_key_cmd_id;
uint32_t group_new_key_cmd_id;
uint32_t group_management_new_key_cmd_id;
uint32_t set_station_cmd_id;
uint32_t set_pmk_cmd_id;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
bool ptk_installed;
bool gtk_installed;
bool igtk_installed;
bool complete;
2018-06-22 02:32:55 +02:00
struct netdev *netdev;
enum connection_type type;
2018-06-22 02:32:55 +02:00
};
struct netdev_ft_over_ds_info {
struct ft_ds_info super;
struct netdev *netdev;
bool parsed : 1;
};
2016-06-01 22:26:02 +02:00
struct netdev {
uint32_t index;
2019-07-08 16:02:58 +02:00
uint64_t wdev_id;
2016-06-01 22:26:02 +02:00
char name[IFNAMSIZ];
uint32_t type;
uint8_t addr[ETH_ALEN];
struct wiphy *wiphy;
2016-07-13 04:26:27 +02:00
unsigned int ifi_flags;
uint32_t frequency;
2016-06-16 22:23:45 +02:00
netdev_event_func_t event_filter;
netdev_connect_cb_t connect_cb;
netdev_disconnect_cb_t disconnect_cb;
netdev_neighbor_report_cb_t neighbor_report_cb;
netdev_command_cb_t adhoc_cb;
void *user_data;
struct eapol_sm *sm;
struct auth_proto *ap;
struct handshake_state *handshake;
netdev: Cancel the CMD_CONNECT genl command on disconnect CMD_DISCONNECT fails on some occasions when CMD_CONNECT is still running. When this happens the DBus disconnect command receives an error reply but iwd's device state is left as disconnected even though there's a connection at the kernel level which times out a few seconds later. If the CMD_CONNECT is cancelled I couldn't reproduce this so far. src/network.c:network_connect() src/network.c:network_connect_psk() src/network.c:network_connect_psk() psk: 69ae3f8b2f84a438cf6a44275913182dd2714510ccb8cbdf8da9dc8b61718560 src/network.c:network_connect_psk() len: 32 src/network.c:network_connect_psk() ask_psk: false src/device.c:device_enter_state() Old State: disconnected, new state: connecting src/scan.c:scan_notify() Scan notification 33 src/device.c:device_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/device.c:device_dbus_disconnect() src/device.c:device_connect_cb() 6, result: 5 src/device.c:device_enter_state() Old State: connecting, new state: disconnecting src/device.c:device_disconnect_cb() 6, success: 0 src/device.c:device_enter_state() Old State: disconnecting, new state: disconnected src/scan.c:scan_notify() Scan notification 34 src/netdev.c:netdev_mlme_notify() MLME notification 19 src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 37 src/netdev.c:netdev_authenticate_event() src/scan.c:get_scan_callback() get_scan_callback src/scan.c:get_scan_done() get_scan_done src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 19 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 38 src/netdev.c:netdev_associate_event() src/netdev.c:netdev_mlme_notify() MLME notification 46 src/netdev.c:netdev_connect_event() <delay> src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 src/netdev.c:netdev_mlme_notify() MLME notification 39 src/netdev.c:netdev_deauthenticate_event()
2016-08-05 14:25:34 +02:00
uint32_t connect_cmd_id;
uint32_t disconnect_cmd_id;
uint32_t join_adhoc_cmd_id;
uint32_t leave_adhoc_cmd_id;
uint32_t set_interface_cmd_id;
uint32_t rekey_offload_cmd_id;
uint32_t qos_map_cmd_id;
uint32_t mac_change_cmd_id;
enum netdev_result result;
uint16_t last_code; /* reason or status, depending on result */
struct l_timeout *neighbor_report_timeout;
struct l_timeout *sa_query_timeout;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
struct l_timeout *group_handshake_timeout;
uint16_t sa_query_id;
uint8_t prev_snonce[32];
int8_t rssi_levels[16];
uint8_t rssi_levels_num;
uint8_t cur_rssi_level_idx;
int8_t cur_rssi;
struct l_timeout *rssi_poll_timeout;
uint32_t rssi_poll_cmd_id;
uint8_t set_mac_once[6];
2016-06-20 12:42:04 +02:00
struct scan_bss *fw_roam_bss;
uint32_t set_powered_cmd_id;
netdev_command_cb_t set_powered_cb;
void *set_powered_user_data;
netdev_destroy_func_t set_powered_destroy;
uint32_t get_station_cmd_id;
netdev_get_station_cb_t get_station_cb;
void *get_station_data;
netdev_destroy_func_t get_station_destroy;
struct l_idle *disconnect_idle;
struct watchlist station_watches;
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
struct l_genl_msg *connect_cmd;
struct l_genl_msg *auth_cmd;
struct wiphy_radio_work_item work;
struct l_queue *ft_ds_list;
bool connected : 1;
bool associated : 1;
bool operational : 1;
bool rekey_offload_support : 1;
bool pae_over_nl80211 : 1;
bool in_ft : 1;
bool cur_rssi_low : 1;
bool use_4addr : 1;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
bool ignore_connect_event : 1;
bool expect_connect_failure : 1;
bool aborting : 1;
bool events_ready : 1;
bool retry_auth : 1;
bool in_reassoc : 1;
2016-06-20 12:42:04 +02:00
};
struct netdev_preauth_state {
netdev_preauthenticate_cb_t cb;
void *user_data;
struct netdev *netdev;
};
2016-06-20 12:42:04 +02:00
struct netdev_watch {
uint32_t id;
netdev_watch_func_t callback;
void *user_data;
2016-06-01 22:26:02 +02:00
};
static struct l_netlink *rtnl = NULL;
static struct l_genl_family *nl80211;
2016-06-01 22:35:26 +02:00
static struct l_queue *netdev_list;
static struct watchlist netdev_watches;
static bool pae_over_nl80211;
static bool mac_per_ssid;
static unsigned int iov_ie_append(struct iovec *iov,
unsigned int n_iov, unsigned int c,
const uint8_t *ie)
{
if (L_WARN_ON(c >= n_iov))
return n_iov;
if (!ie)
return c;
iov[c].iov_base = (void *) ie;
iov[c].iov_len = ie[1] + 2;
return c + 1u;
}
const char *netdev_iftype_to_string(uint32_t iftype)
{
switch (iftype) {
case NL80211_IFTYPE_ADHOC:
return "ad-hoc";
case NL80211_IFTYPE_STATION:
return "station";
case NL80211_IFTYPE_AP:
return "ap";
case NL80211_IFTYPE_P2P_CLIENT:
return "p2p-client";
case NL80211_IFTYPE_P2P_GO:
return "p2p-go";
case NL80211_IFTYPE_P2P_DEVICE:
return "p2p-device";
default:
break;
}
return NULL;
}
static inline bool is_offload(struct handshake_state *hs)
{
struct netdev_handshake_state *nhs =
l_container_of(hs, struct netdev_handshake_state, super);
if (!nhs)
return false;
switch (nhs->type) {
case CONNECTION_TYPE_SOFTMAC:
case CONNECTION_TYPE_FULLMAC:
/*
* 8021x offload does not quite fit into the same category of PSK
* offload. First the netdev_connect_event comes prior to EAP meaning
* the handshake is not done at this point. In addition it still
* requires EAP take place in userspace meaning IWD needs an eapol_sm.
* Because of this, and our prior use of 'is_offload', it does not fit
* into the same category and will need to be handled specially.
*/
case CONNECTION_TYPE_8021X_OFFLOAD:
return false;
case CONNECTION_TYPE_SAE_OFFLOAD:
case CONNECTION_TYPE_PSK_OFFLOAD:
return true;
}
return false;
}
static unsigned int netdev_populate_common_ies(struct netdev *netdev,
struct handshake_state *hs,
struct l_genl_msg *msg,
struct iovec *iov,
unsigned int n_iov,
unsigned int c_iov)
{
const uint8_t *extended_capabilities;
const uint8_t *rm_enabled_capabilities;
extended_capabilities = wiphy_get_extended_capabilities(netdev->wiphy,
netdev->type);
c_iov = iov_ie_append(iov, n_iov, c_iov, extended_capabilities);
rm_enabled_capabilities =
wiphy_get_rm_enabled_capabilities(netdev->wiphy);
c_iov = iov_ie_append(iov, n_iov, c_iov, rm_enabled_capabilities);
if (rm_enabled_capabilities)
l_genl_msg_append_attr(msg, NL80211_ATTR_USE_RRM, 0, NULL);
c_iov = iov_ie_append(iov, n_iov, c_iov, hs->vendor_ies);
return c_iov;
}
/* Cancels ongoing GTK/IGTK related commands (if any) */
static void netdev_handshake_state_cancel_rekey(
struct netdev_handshake_state *nhs)
2018-06-22 02:32:55 +02:00
{
if (nhs->group_new_key_cmd_id) {
l_genl_family_cancel(nl80211, nhs->group_new_key_cmd_id);
nhs->group_new_key_cmd_id = 0;
}
if (nhs->group_management_new_key_cmd_id) {
l_genl_family_cancel(nl80211,
nhs->group_management_new_key_cmd_id);
nhs->group_management_new_key_cmd_id = 0;
}
}
static void netdev_handshake_state_cancel_all(
struct netdev_handshake_state *nhs)
{
if (nhs->pairwise_new_key_cmd_id) {
l_genl_family_cancel(nl80211, nhs->pairwise_new_key_cmd_id);
nhs->pairwise_new_key_cmd_id = 0;
}
netdev_handshake_state_cancel_rekey(nhs);
2018-06-22 02:32:55 +02:00
if (nhs->set_station_cmd_id) {
l_genl_family_cancel(nl80211, nhs->set_station_cmd_id);
nhs->set_station_cmd_id = 0;
}
if (nhs->set_pmk_cmd_id) {
l_genl_family_cancel(nl80211, nhs->set_pmk_cmd_id);
nhs->set_pmk_cmd_id = 0;
}
}
2018-06-22 02:32:55 +02:00
static void netdev_handshake_state_free(struct handshake_state *hs)
{
struct netdev_handshake_state *nhs =
2019-04-03 18:47:09 +02:00
l_container_of(hs, struct netdev_handshake_state, super);
netdev_handshake_state_cancel_all(nhs);
2018-06-22 02:32:55 +02:00
l_free(nhs);
}
struct handshake_state *netdev_handshake_state_new(struct netdev *netdev)
{
struct netdev_handshake_state *nhs;
nhs = l_new(struct netdev_handshake_state, 1);
nhs->super.ifindex = netdev->index;
nhs->super.free = netdev_handshake_state_free;
nhs->netdev = netdev;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
/*
* Since GTK/IGTK are optional (NO_GROUP_TRAFFIC), we set them as
2020-01-21 07:21:38 +01:00
* 'installed' upon initialization. If/When the gtk/igtk callback is
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
* called they will get set to false until we have received a successful
* callback from nl80211. From these callbacks we can check that all
* the keys have been installed, and only then trigger the handshake
* complete callback.
*/
nhs->gtk_installed = true;
nhs->igtk_installed = true;
2018-06-22 02:32:55 +02:00
return &nhs->super;
}
struct wiphy *netdev_get_wiphy(struct netdev *netdev)
{
return netdev->wiphy;
}
2016-06-01 22:27:39 +02:00
const uint8_t *netdev_get_address(struct netdev *netdev)
{
return netdev->addr;
}
2016-06-01 22:27:05 +02:00
uint32_t netdev_get_ifindex(struct netdev *netdev)
{
return netdev->index;
}
2019-07-08 16:02:58 +02:00
uint64_t netdev_get_wdev_id(struct netdev *netdev)
{
return netdev->wdev_id;
}
enum netdev_iftype netdev_get_iftype(struct netdev *netdev)
2016-06-02 00:04:10 +02:00
{
return netdev->type;
2016-06-02 00:04:10 +02:00
}
2016-06-02 00:05:56 +02:00
const char *netdev_get_name(struct netdev *netdev)
{
return netdev->name;
}
2016-06-20 12:42:03 +02:00
bool netdev_get_is_up(struct netdev *netdev)
{
bool powered = (netdev->ifi_flags & IFF_UP) != 0;
/*
* If we are in the middle of changing the MAC we are in somewhat of a
* no mans land. Technically the iface may be down, but since we are
* not emitting any netdev DOWN events we want netdev_get_is_up to
* reflect the same state. Once MAC changing finishes any pending
* DOWN events will be emitted.
*/
if (netdev->mac_change_cmd_id && !powered)
return true;
return powered;
2016-07-13 04:26:27 +02:00
}
struct handshake_state *netdev_get_handshake(struct netdev *netdev)
{
return netdev->handshake;
}
2018-08-20 06:12:14 +02:00
const char *netdev_get_path(struct netdev *netdev)
{
static char path[256];
L_WARN_ON(snprintf(path, sizeof(path), "%s/%u",
wiphy_get_path(netdev->wiphy),
netdev->index) >= (int) sizeof(path));
path[sizeof(path) - 1] = '\0';
2018-08-20 06:12:14 +02:00
return path;
}
2016-07-13 04:26:27 +02:00
static void netdev_set_powered_result(int error, uint16_t type,
const void *data,
uint32_t len, void *user_data)
{
struct netdev *netdev = user_data;
2016-07-13 04:26:27 +02:00
if (netdev->set_powered_cb)
netdev->set_powered_cb(netdev, error,
netdev->set_powered_user_data);
2016-07-13 04:26:27 +02:00
netdev->set_powered_cb = NULL;
2016-07-13 04:26:27 +02:00
}
static void netdev_set_powered_destroy(void *user_data)
{
struct netdev *netdev = user_data;
2016-07-13 04:26:27 +02:00
netdev->set_powered_cmd_id = 0;
2016-07-13 04:26:27 +02:00
if (netdev->set_powered_destroy)
netdev->set_powered_destroy(netdev->set_powered_user_data);
2016-07-13 04:26:27 +02:00
netdev->set_powered_destroy = NULL;
netdev->set_powered_user_data = NULL;
2016-07-13 04:26:27 +02:00
}
int netdev_set_powered(struct netdev *netdev, bool powered,
netdev_command_cb_t callback, void *user_data,
netdev_destroy_func_t destroy)
{
if (netdev->set_powered_cmd_id ||
netdev->set_interface_cmd_id)
return -EBUSY;
netdev->set_powered_cmd_id =
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, netdev->index, powered,
netdev_set_powered_result, netdev,
netdev_set_powered_destroy);
if (!netdev->set_powered_cmd_id)
return -EIO;
netdev->set_powered_cb = callback;
netdev->set_powered_user_data = user_data;
netdev->set_powered_destroy = destroy;
2016-07-13 04:26:27 +02:00
return 0;
2016-06-20 12:42:03 +02:00
}
static bool netdev_parse_bitrate(struct l_genl_attr *attr,
enum diagnostic_mcs_type *type_out,
uint32_t *rate_out,
uint8_t *mcs_out)
{
uint16_t type, len;
const void *data;
uint32_t rate = 0;
uint8_t mcs = 0;
enum diagnostic_mcs_type mcs_type = DIAGNOSTIC_MCS_TYPE_NONE;
while (l_genl_attr_next(attr, &type, &len, &data)) {
switch (type) {
case NL80211_RATE_INFO_BITRATE32:
if (len != 4)
return false;
rate = l_get_u32(data);
break;
case NL80211_RATE_INFO_MCS:
if (len != 1)
return false;
mcs = l_get_u8(data);
mcs_type = DIAGNOSTIC_MCS_TYPE_HT;
break;
case NL80211_RATE_INFO_VHT_MCS:
if (len != 1)
return false;
mcs = l_get_u8(data);
mcs_type = DIAGNOSTIC_MCS_TYPE_VHT;
break;
case NL80211_RATE_INFO_HE_MCS:
if (len != 1)
return false;
mcs = l_get_u8(data);
mcs_type = DIAGNOSTIC_MCS_TYPE_HE;
break;
}
}
if (!rate)
return false;
*type_out = mcs_type;
*rate_out = rate;
if (mcs_type != DIAGNOSTIC_MCS_TYPE_NONE)
*mcs_out = mcs;
return true;
}
static bool netdev_parse_sta_info(struct l_genl_attr *attr,
struct diagnostic_station_info *info)
{
uint16_t type, len;
const void *data;
struct l_genl_attr nested;
while (l_genl_attr_next(attr, &type, &len, &data)) {
switch (type) {
case NL80211_STA_INFO_SIGNAL:
if (len != 1)
return false;
info->cur_rssi = *(const int8_t *) data;
info->have_cur_rssi = true;
break;
case NL80211_STA_INFO_SIGNAL_AVG:
if (len != 1)
return false;
info->avg_rssi = *(const int8_t *) data;
info->have_avg_rssi = true;
break;
case NL80211_STA_INFO_RX_BITRATE:
if (!l_genl_attr_recurse(attr, &nested))
return false;
if (!netdev_parse_bitrate(&nested, &info->rx_mcs_type,
&info->rx_bitrate,
&info->rx_mcs))
return false;
info->have_rx_bitrate = true;
if (info->rx_mcs_type != DIAGNOSTIC_MCS_TYPE_NONE)
info->have_rx_mcs = true;
break;
case NL80211_STA_INFO_TX_BITRATE:
if (!l_genl_attr_recurse(attr, &nested))
return false;
if (!netdev_parse_bitrate(&nested, &info->tx_mcs_type,
&info->tx_bitrate,
&info->tx_mcs))
return false;
info->have_tx_bitrate = true;
if (info->tx_mcs_type != DIAGNOSTIC_MCS_TYPE_NONE)
info->have_tx_mcs = true;
break;
case NL80211_STA_INFO_EXPECTED_THROUGHPUT:
if (len != 4)
return false;
info->expected_throughput = l_get_u32(data);
info->have_expected_throughput = true;
break;
}
}
return true;
}
static void netdev_set_rssi_level_idx(struct netdev *netdev)
{
uint8_t new_level;
for (new_level = 0; new_level < netdev->rssi_levels_num; new_level++)
if (netdev->cur_rssi >= netdev->rssi_levels[new_level])
break;
netdev->cur_rssi_level_idx = new_level;
}
static void netdev_rssi_poll_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_attr attr, nested;
uint16_t type, len;
const void *data;
bool found;
struct diagnostic_station_info info;
uint8_t prev_rssi_level_idx = netdev->cur_rssi_level_idx;
netdev->rssi_poll_cmd_id = 0;
if (!l_genl_attr_init(&attr, msg))
goto done;
found = false;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
if (type != NL80211_ATTR_STA_INFO)
continue;
if (!l_genl_attr_recurse(&attr, &nested))
goto done;
if (!netdev_parse_sta_info(&nested, &info))
goto done;
found = true;
break;
}
if (!found || !info.have_cur_rssi)
goto done;
netdev->cur_rssi = info.cur_rssi;
/*
* Note we don't have to handle LOW_SIGNAL_THRESHOLD here. The
* CQM single threshold RSSI monitoring should work even if the
* kernel driver doesn't support multiple thresholds. So the
* polling only handles the client-supplied threshold list.
*/
netdev_set_rssi_level_idx(netdev);
if (netdev->cur_rssi_level_idx != prev_rssi_level_idx)
netdev->event_filter(netdev, NETDEV_EVENT_RSSI_LEVEL_NOTIFY,
&netdev->cur_rssi_level_idx,
netdev->user_data);
done:
/* Rearm timer */
l_timeout_modify(netdev->rssi_poll_timeout, 6);
}
static void netdev_rssi_poll(struct l_timeout *timeout, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN,
netdev->handshake->aa);
netdev->rssi_poll_cmd_id = l_genl_family_send(nl80211, msg,
netdev_rssi_poll_cb,
netdev, NULL);
}
/* To be called whenever operational or rssi_levels_num are updated */
static void netdev_rssi_polling_update(struct netdev *netdev)
{
if (wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CQM_RSSI_LIST))
return;
if (netdev->operational && netdev->rssi_levels_num > 0) {
if (netdev->rssi_poll_timeout)
return;
netdev->rssi_poll_timeout =
l_timeout_create(1, netdev_rssi_poll, netdev, NULL);
} else {
if (!netdev->rssi_poll_timeout)
return;
l_timeout_remove(netdev->rssi_poll_timeout);
netdev->rssi_poll_timeout = NULL;
if (netdev->rssi_poll_cmd_id) {
l_genl_family_cancel(nl80211, netdev->rssi_poll_cmd_id);
netdev->rssi_poll_cmd_id = 0;
}
}
}
static void netdev_preauth_destroy(void *data)
{
struct netdev_preauth_state *state = data;
if (state->cb)
state->cb(state->netdev, NETDEV_RESULT_ABORTED, NULL,
state->user_data);
l_free(state);
}
static void netdev_ft_ds_entry_free(void *data)
{
struct netdev_ft_over_ds_info *info = data;
ft_ds_info_free(&info->super);
}
static void netdev_connect_free(struct netdev *netdev)
{
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
}
if (netdev->ap) {
auth_proto_free(netdev->ap);
netdev->ap = NULL;
}
eapol_preauth_cancel(netdev->index);
if (netdev->handshake) {
handshake_state_free(netdev->handshake);
netdev->handshake = NULL;
}
if (netdev->neighbor_report_cb) {
netdev->neighbor_report_cb(netdev, -ENOTCONN, NULL, 0,
netdev->user_data);
netdev->neighbor_report_cb = NULL;
l_timeout_remove(netdev->neighbor_report_timeout);
}
if (netdev->sa_query_timeout) {
l_timeout_remove(netdev->sa_query_timeout);
netdev->sa_query_timeout = NULL;
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
if (netdev->group_handshake_timeout) {
l_timeout_remove(netdev->group_handshake_timeout);
netdev->group_handshake_timeout = NULL;
}
netdev->associated = false;
netdev->operational = false;
netdev->connected = false;
netdev->connect_cb = NULL;
netdev->event_filter = NULL;
netdev->user_data = NULL;
netdev->result = NETDEV_RESULT_OK;
netdev->last_code = 0;
netdev->in_ft = false;
netdev->in_reassoc = false;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
netdev->ignore_connect_event = false;
netdev->expect_connect_failure = false;
netdev->cur_rssi_low = false;
if (netdev->connect_cmd) {
l_genl_msg_unref(netdev->connect_cmd);
netdev->connect_cmd = NULL;
}
netdev_rssi_polling_update(netdev);
netdev: Cancel the CMD_CONNECT genl command on disconnect CMD_DISCONNECT fails on some occasions when CMD_CONNECT is still running. When this happens the DBus disconnect command receives an error reply but iwd's device state is left as disconnected even though there's a connection at the kernel level which times out a few seconds later. If the CMD_CONNECT is cancelled I couldn't reproduce this so far. src/network.c:network_connect() src/network.c:network_connect_psk() src/network.c:network_connect_psk() psk: 69ae3f8b2f84a438cf6a44275913182dd2714510ccb8cbdf8da9dc8b61718560 src/network.c:network_connect_psk() len: 32 src/network.c:network_connect_psk() ask_psk: false src/device.c:device_enter_state() Old State: disconnected, new state: connecting src/scan.c:scan_notify() Scan notification 33 src/device.c:device_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/device.c:device_dbus_disconnect() src/device.c:device_connect_cb() 6, result: 5 src/device.c:device_enter_state() Old State: connecting, new state: disconnecting src/device.c:device_disconnect_cb() 6, success: 0 src/device.c:device_enter_state() Old State: disconnecting, new state: disconnected src/scan.c:scan_notify() Scan notification 34 src/netdev.c:netdev_mlme_notify() MLME notification 19 src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 37 src/netdev.c:netdev_authenticate_event() src/scan.c:get_scan_callback() get_scan_callback src/scan.c:get_scan_done() get_scan_done src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 19 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 38 src/netdev.c:netdev_associate_event() src/netdev.c:netdev_mlme_notify() MLME notification 46 src/netdev.c:netdev_connect_event() <delay> src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 src/netdev.c:netdev_mlme_notify() MLME notification 39 src/netdev.c:netdev_deauthenticate_event()
2016-08-05 14:25:34 +02:00
if (netdev->connect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
netdev->connect_cmd_id = 0;
} else if (netdev->disconnect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->disconnect_cmd_id);
netdev->disconnect_cmd_id = 0;
netdev: Cancel the CMD_CONNECT genl command on disconnect CMD_DISCONNECT fails on some occasions when CMD_CONNECT is still running. When this happens the DBus disconnect command receives an error reply but iwd's device state is left as disconnected even though there's a connection at the kernel level which times out a few seconds later. If the CMD_CONNECT is cancelled I couldn't reproduce this so far. src/network.c:network_connect() src/network.c:network_connect_psk() src/network.c:network_connect_psk() psk: 69ae3f8b2f84a438cf6a44275913182dd2714510ccb8cbdf8da9dc8b61718560 src/network.c:network_connect_psk() len: 32 src/network.c:network_connect_psk() ask_psk: false src/device.c:device_enter_state() Old State: disconnected, new state: connecting src/scan.c:scan_notify() Scan notification 33 src/device.c:device_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/device.c:device_dbus_disconnect() src/device.c:device_connect_cb() 6, result: 5 src/device.c:device_enter_state() Old State: connecting, new state: disconnecting src/device.c:device_disconnect_cb() 6, success: 0 src/device.c:device_enter_state() Old State: disconnecting, new state: disconnected src/scan.c:scan_notify() Scan notification 34 src/netdev.c:netdev_mlme_notify() MLME notification 19 src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 37 src/netdev.c:netdev_authenticate_event() src/scan.c:get_scan_callback() get_scan_callback src/scan.c:get_scan_done() get_scan_done src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 19 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 38 src/netdev.c:netdev_associate_event() src/netdev.c:netdev_mlme_notify() MLME notification 46 src/netdev.c:netdev_connect_event() <delay> src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 src/netdev.c:netdev_mlme_notify() MLME notification 39 src/netdev.c:netdev_deauthenticate_event()
2016-08-05 14:25:34 +02:00
}
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
}
static void netdev_connect_failed(struct netdev *netdev,
enum netdev_result result,
uint16_t status_or_reason)
{
netdev_connect_cb_t connect_cb = netdev->connect_cb;
netdev_event_func_t event_filter = netdev->event_filter;
void *connect_data = netdev->user_data;
2016-12-20 17:31:33 +01:00
/* Done this way to allow re-entrant netdev_connect calls */
netdev_connect_free(netdev);
if (connect_cb)
connect_cb(netdev, result, &status_or_reason, connect_data);
else if (event_filter) {
/* NETDEV_EVENT_DISCONNECT_BY_SME expects a reason code */
if (result != NETDEV_RESULT_HANDSHAKE_FAILED)
status_or_reason = MMPDU_REASON_CODE_UNSPECIFIED;
event_filter(netdev, NETDEV_EVENT_DISCONNECT_BY_SME,
&status_or_reason,
connect_data);
}
}
static void netdev_disconnect_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
netdev->disconnect_cmd_id = 0;
netdev_connect_failed(netdev, netdev->result, netdev->last_code);
}
2016-06-01 22:35:26 +02:00
static void netdev_free(void *data)
{
struct netdev *netdev = data;
l_debug("Freeing netdev %s[%d]", netdev->name, netdev->index);
netdev->ifi_flags &= ~IFF_UP;
if (netdev->events_ready)
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DEL);
if (netdev->neighbor_report_cb) {
netdev->neighbor_report_cb(netdev, -ENODEV, NULL, 0,
netdev->user_data);
netdev->neighbor_report_cb = NULL;
l_timeout_remove(netdev->neighbor_report_timeout);
}
netdev: Better detect connecting state netdev_free relies on netdev->connected being set to detect whether a connection is in progress. This variable is only set once the driver has been connected however, so for situations where a CMD_CONNECT is still 'in flight' or if the wiphy work is still pending, the ongoing connection will not be canceled. Fix that by being more thorough when trying to detect that a connection is in progress. src/wiphy.c:wiphy_radio_work_next() Starting work item 2 Terminate src/netdev.c:netdev_free() Freeing netdev wlan0[9] src/device.c:device_free() src/station.c:station_free() src/netconfig.c:netconfig_destroy() Removing scan context for wdev c src/scan.c:scan_context_free() sc: 0x4a44c80 src/netdev.c:netdev_mlme_notify() MLME notification New Station(19) src/netdev.c:netdev_link_notify() event 16 on ifindex 9 ==6356== Invalid write of size 4 ==6356== at 0x40A253: netdev_cmd_connect_cb (netdev.c:2522) ==6356== by 0x4A8886: process_unicast (genl.c:986) ==6356== by 0x4A8C48: received_data (genl.c:1098) ==6356== by 0x4A3DFD: io_callback (io.c:120) ==6356== by 0x4A2799: l_main_iterate (main.c:478) ==6356== by 0x4A28DA: l_main_run (main.c:525) ==6356== by 0x4A2BF2: l_main_run_with_signal (main.c:647) ==6356== by 0x404D27: main (main.c:542) ==6356== Address 0x4a3e418 is 152 bytes inside a block of size 472 free'd ==6356== at 0x48399CB: free (vg_replace_malloc.c:538) ==6356== by 0x49996F: l_free (util.c:136) ==6356== by 0x406662: netdev_free (netdev.c:886) ==6356== by 0x4129C2: netdev_shutdown (netdev.c:5980) ==6356== by 0x403A14: iwd_shutdown (main.c:79) ==6356== by 0x403A7D: signal_handler (main.c:90) ==6356== by 0x4A2AFB: sigint_handler (main.c:612) ==6356== by 0x4A2F3B: handle_callback (signal.c:78) ==6356== by 0x4A3030: signalfd_read_cb (signal.c:104) ==6356== by 0x4A3DFD: io_callback (io.c:120) ==6356== by 0x4A2799: l_main_iterate (main.c:478) ==6356== by 0x4A28DA: l_main_run (main.c:525) ==6356== Block was alloc'd at ==6356== at 0x483879F: malloc (vg_replace_malloc.c:307) ==6356== by 0x49983B: l_malloc (util.c:62) ==6356== by 0x4121BD: netdev_create_from_genl (netdev.c:5776) ==6356== by 0x451F6F: manager_new_station_interface_cb (manager.c:173) ==6356== by 0x4A8886: process_unicast (genl.c:986) ==6356== by 0x4A8C48: received_data (genl.c:1098) ==6356== by 0x4A3DFD: io_callback (io.c:120) ==6356== by 0x4A2799: l_main_iterate (main.c:478) ==6356== by 0x4A28DA: l_main_run (main.c:525) ==6356== by 0x4A2BF2: l_main_run_with_signal (main.c:647) ==6356== by 0x404D27: main (main.c:542)
2021-06-02 01:16:03 +02:00
if (netdev->connected || netdev->connect_cmd_id || netdev->work.id)
netdev_connect_free(netdev);
if (netdev->disconnect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->disconnect_cmd_id);
netdev->disconnect_cmd_id = 0;
if (netdev->disconnect_cb)
netdev->disconnect_cb(netdev, true, netdev->user_data);
netdev->disconnect_cb = NULL;
netdev->user_data = NULL;
}
2016-06-28 23:58:17 +02:00
if (netdev->disconnect_idle) {
l_idle_remove(netdev->disconnect_idle);
netdev->disconnect_idle = NULL;
}
if (netdev->join_adhoc_cmd_id) {
l_genl_family_cancel(nl80211, netdev->join_adhoc_cmd_id);
netdev->join_adhoc_cmd_id = 0;
}
if (netdev->leave_adhoc_cmd_id) {
l_genl_family_cancel(nl80211, netdev->leave_adhoc_cmd_id);
netdev->leave_adhoc_cmd_id = 0;
}
if (netdev->set_powered_cmd_id) {
l_netlink_cancel(rtnl, netdev->set_powered_cmd_id);
netdev->set_powered_cmd_id = 0;
}
if (netdev->rekey_offload_cmd_id) {
l_genl_family_cancel(nl80211, netdev->rekey_offload_cmd_id);
netdev->rekey_offload_cmd_id = 0;
}
if (netdev->qos_map_cmd_id) {
l_genl_family_cancel(nl80211, netdev->qos_map_cmd_id);
netdev->qos_map_cmd_id = 0;
}
if (netdev->mac_change_cmd_id) {
l_netlink_cancel(rtnl, netdev->mac_change_cmd_id);
netdev->mac_change_cmd_id = 0;
}
if (netdev->get_station_cmd_id) {
l_genl_family_cancel(nl80211, netdev->get_station_cmd_id);
netdev->get_station_cmd_id = 0;
}
if (netdev->fw_roam_bss)
scan_bss_free(netdev->fw_roam_bss);
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
scan_wdev_remove(netdev->wdev_id);
watchlist_destroy(&netdev->station_watches);
l_io_destroy(netdev->pae_io);
l_free(netdev);
}
static void netdev_shutdown_one(void *data, void *user_data)
{
struct netdev *netdev = data;
if (netdev_get_is_up(netdev))
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, netdev->index, false,
NULL, NULL, NULL);
2016-06-01 22:35:26 +02:00
}
static bool netdev_match(const void *a, const void *b)
{
const struct netdev *netdev = a;
uint32_t ifindex = L_PTR_TO_UINT(b);
return (netdev->index == ifindex);
}
struct netdev *netdev_find(int ifindex)
{
return l_queue_find(netdev_list, netdev_match, L_UINT_TO_PTR(ifindex));
}
/* Threshold RSSI for roaming to trigger, configurable in main.conf */
static int LOW_SIGNAL_THRESHOLD;
static int LOW_SIGNAL_THRESHOLD_5GHZ;
static void netdev_cqm_event_rssi_threshold(struct netdev *netdev,
uint32_t rssi_event)
{
int event;
if (!netdev->operational)
return;
if (rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW &&
rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH)
return;
if (!netdev->event_filter)
return;
netdev->cur_rssi_low =
(rssi_event == NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW);
event = netdev->cur_rssi_low ? NETDEV_EVENT_RSSI_THRESHOLD_LOW :
NETDEV_EVENT_RSSI_THRESHOLD_HIGH;
netdev->event_filter(netdev, event, NULL, netdev->user_data);
}
static void netdev_rssi_level_init(struct netdev *netdev)
{
if (netdev->connected && netdev->rssi_levels_num)
netdev_set_rssi_level_idx(netdev);
}
static void netdev_cqm_event_rssi_value(struct netdev *netdev, int rssi_val)
{
bool new_rssi_low;
uint8_t prev_rssi_level_idx = netdev->cur_rssi_level_idx;
int threshold = netdev->frequency > 4000 ? LOW_SIGNAL_THRESHOLD_5GHZ :
LOW_SIGNAL_THRESHOLD;
if (!netdev->connected)
return;
if (rssi_val > 127)
rssi_val = 127;
else if (rssi_val < -127)
rssi_val = -127;
netdev->cur_rssi = rssi_val;
if (!netdev->event_filter)
return;
new_rssi_low = rssi_val < threshold;
if (netdev->cur_rssi_low != new_rssi_low) {
int event = new_rssi_low ?
NETDEV_EVENT_RSSI_THRESHOLD_LOW :
NETDEV_EVENT_RSSI_THRESHOLD_HIGH;
netdev->cur_rssi_low = new_rssi_low;
netdev->event_filter(netdev, event, NULL, netdev->user_data);
}
if (!netdev->rssi_levels_num)
return;
netdev_set_rssi_level_idx(netdev);
if (netdev->cur_rssi_level_idx != prev_rssi_level_idx)
netdev->event_filter(netdev, NETDEV_EVENT_RSSI_LEVEL_NOTIFY,
&netdev->cur_rssi_level_idx,
netdev->user_data);
}
static void netdev_cqm_event(struct l_genl_msg *msg, struct netdev *netdev)
{
struct l_genl_attr attr;
struct l_genl_attr nested;
uint16_t type, len;
const void *data;
uint32_t *rssi_event = NULL;
int32_t *rssi_val = NULL;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_CQM:
if (!l_genl_attr_recurse(&attr, &nested))
return;
while (l_genl_attr_next(&nested, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT:
if (len != 4)
continue;
rssi_event = (uint32_t *) data;
break;
case NL80211_ATTR_CQM_RSSI_LEVEL:
if (len != 4)
continue;
rssi_val = (int32_t *) data;
break;
}
}
break;
}
}
if (rssi_event) {
if (rssi_val)
netdev_cqm_event_rssi_value(netdev, *rssi_val);
else
netdev_cqm_event_rssi_threshold(netdev, *rssi_event);
}
}
static void netdev_rekey_offload_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
struct l_genl_attr nested;
uint16_t type, len;
const void *data;
uint64_t replay_ctr;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
2016-07-20 00:52:36 +02:00
if (type != NL80211_ATTR_REKEY_DATA)
continue;
if (!l_genl_attr_recurse(&attr, &nested))
return;
while (l_genl_attr_next(&nested, &type, &len, &data)) {
if (type != NL80211_REKEY_DATA_REPLAY_CTR)
continue;
if (len != sizeof(uint64_t)) {
l_warn("Invalid replay_ctr");
return;
2016-07-20 00:52:36 +02:00
}
2016-07-20 00:52:36 +02:00
replay_ctr = *((uint64_t *) data);
__eapol_update_replay_counter(netdev->index,
netdev->addr,
2016-12-12 18:34:21 +01:00
netdev->handshake->aa,
replay_ctr);
2016-07-20 00:52:36 +02:00
return;
}
}
}
static void netdev_disconnect_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
uint16_t reason_code = 0;
bool disconnect_by_ap = false;
netdev_event_func_t event_filter;
void *event_data;
l_debug("");
if (!netdev->connected || netdev->disconnect_cmd_id > 0 ||
netdev->in_ft || netdev->in_reassoc)
return;
if (!l_genl_attr_init(&attr, msg)) {
l_error("attr init failed");
return;
}
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_REASON_CODE:
if (len != sizeof(uint16_t))
l_warn("Invalid reason code attribute");
else
reason_code = *((uint16_t *) data);
break;
case NL80211_ATTR_DISCONNECTED_BY_AP:
disconnect_by_ap = true;
break;
}
}
l_info("Received Deauthentication event, reason: %hu, from_ap: %s",
reason_code, disconnect_by_ap ? "true" : "false");
event_filter = netdev->event_filter;
event_data = netdev->user_data;
netdev_connect_free(netdev);
if (!event_filter)
return;
if (disconnect_by_ap)
event_filter(netdev, NETDEV_EVENT_DISCONNECT_BY_AP,
&reason_code, event_data);
else
event_filter(netdev, NETDEV_EVENT_DISCONNECT_BY_SME,
&reason_code, event_data);
}
static void netdev_cmd_disconnect_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
void *disconnect_data;
netdev_disconnect_cb_t disconnect_cb;
bool r;
netdev->disconnect_cmd_id = 0;
netdev->aborting = false;
if (!netdev->disconnect_cb) {
netdev->user_data = NULL;
return;
}
disconnect_data = netdev->user_data;
disconnect_cb = netdev->disconnect_cb;
netdev->user_data = NULL;
netdev->disconnect_cb = NULL;
if (l_genl_msg_get_error(msg) < 0)
r = false;
else
r = true;
disconnect_cb(netdev, r, disconnect_data);
}
static struct l_genl_msg *netdev_build_cmd_disconnect(struct netdev *netdev,
uint16_t reason_code)
{
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_DISCONNECT, 64);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_REASON_CODE, 2, &reason_code);
return msg;
}
static void netdev_deauthenticate_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const struct mmpdu_header *hdr = NULL;
uint16_t reason_code;
l_debug("");
/*
* If we got to the association phase, process the connect event
* instead
*/
if (!netdev->connected || netdev->associated)
return;
/*
* Handle the bizarre case of AP accepting authentication, then
* deauthenticating immediately afterwards
*/
if (L_WARN_ON(!l_genl_attr_init(&attr, msg)))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_FRAME:
hdr = mpdu_validate(data, len);
break;
}
}
if (L_WARN_ON(!hdr))
return;
/* Ignore any locally generated frames */
if (!memcmp(hdr->address_2, netdev->addr, sizeof(netdev->addr)))
return;
reason_code = l_get_u8(mmpdu_body(hdr));
l_info("deauth event, src="MAC" dest="MAC" bssid="MAC" reason=%u",
MAC_STR(hdr->address_2), MAC_STR(hdr->address_1),
MAC_STR(hdr->address_3), reason_code);
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static struct l_genl_msg *netdev_build_cmd_deauthenticate(struct netdev *netdev,
uint16_t reason_code)
{
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_DEAUTHENTICATE, 128);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_REASON_CODE, 2, &reason_code);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN,
2016-12-12 18:34:21 +01:00
netdev->handshake->aa);
return msg;
}
static struct l_genl_msg *netdev_build_cmd_del_station(struct netdev *netdev,
const uint8_t *sta,
uint16_t reason_code,
bool disassociate)
{
struct l_genl_msg *msg;
uint8_t subtype = disassociate ?
MPDU_MANAGEMENT_SUBTYPE_DISASSOCIATION :
MPDU_MANAGEMENT_SUBTYPE_DEAUTHENTICATION;
msg = l_genl_msg_new_sized(NL80211_CMD_DEL_STATION, 64);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, 6, sta);
l_genl_msg_append_attr(msg, NL80211_ATTR_MGMT_SUBTYPE, 1, &subtype);
l_genl_msg_append_attr(msg, NL80211_ATTR_REASON_CODE, 2, &reason_code);
return msg;
}
static void netdev_del_sta_cb(struct l_genl_msg *msg, void *user_data)
{
int err = l_genl_msg_get_error(msg);
const char *ext_error;
if (err >= 0)
return;
ext_error = l_genl_msg_get_extended_error(msg);
l_error("DEL_STATION failed: %s",
ext_error ? ext_error : strerror(-err));
}
int netdev_del_station(struct netdev *netdev, const uint8_t *sta,
uint16_t reason_code, bool disassociate)
{
struct l_genl_msg *msg;
msg = netdev_build_cmd_del_station(netdev, sta, reason_code,
disassociate);
if (!l_genl_family_send(nl80211, msg, netdev_del_sta_cb, NULL, NULL))
return -EIO;
return 0;
}
static void netdev_operstate_cb(int error, uint16_t type,
const void *data,
uint32_t len, void *user_data)
{
if (!error)
return;
l_debug("netdev: %u, error: %s", L_PTR_TO_UINT(user_data),
strerror(-error));
}
static void netdev_connect_ok(struct netdev *netdev)
{
2020-02-15 10:48:39 +01:00
l_rtnl_set_linkmode_and_operstate(rtnl, netdev->index,
IF_LINK_MODE_DORMANT, IF_OPER_UP,
netdev_operstate_cb,
L_UINT_TO_PTR(netdev->index), NULL);
netdev->operational = true;
if (netdev->fw_roam_bss) {
if (netdev->event_filter)
netdev->event_filter(netdev, NETDEV_EVENT_ROAMED,
netdev->fw_roam_bss,
netdev->user_data);
else
scan_bss_free(netdev->fw_roam_bss);
netdev->fw_roam_bss = NULL;
}
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
if (netdev->connect_cb) {
netdev->connect_cb(netdev, NETDEV_RESULT_OK, NULL,
netdev->user_data);
netdev->connect_cb = NULL;
}
netdev_rssi_polling_update(netdev);
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
2018-06-22 02:32:55 +02:00
static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs,
int err)
{
2018-06-22 02:32:55 +02:00
struct netdev *netdev = nhs->netdev;
struct l_genl_msg *msg;
/*
* Something went wrong with our sequence:
* 1. new_key(ptk)
* 2. new_key(gtk) [optional]
* 3. new_key(igtk) [optional]
* 4. rekey offload [optional]
* 5. set_station
*
* Cancel all pending commands, then de-authenticate
*/
netdev_handshake_state_cancel_all(nhs);
if (netdev->rekey_offload_cmd_id) {
l_genl_family_cancel(nl80211, netdev->rekey_offload_cmd_id);
netdev->rekey_offload_cmd_id = 0;
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
if (netdev->group_handshake_timeout) {
l_timeout_remove(netdev->group_handshake_timeout);
netdev->group_handshake_timeout = NULL;
}
switch (netdev->type) {
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
netdev: Detect netdev going down early In case the netdev is brought down while we're trying to connect, try to detect this and fail early instead of trying to send additional commands. src/station.c:station_enter_state() Old State: disconnected, new state: connecting src/station.c:station_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification Connect(46) src/netdev.c:netdev_connect_event() src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_1_of_4() ifindex=4 src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_3_of_4() ifindex=4 src/netdev.c:netdev_set_gtk() 4 src/station.c:station_handshake_event() Setting keys src/netdev.c:netdev_set_tk() 4 src/netdev.c:netdev_set_rekey_offload() 4 New Key for Group Key failed for ifindex: 4:Network is down src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/station.c:station_free() src/netdev.c:netdev_mlme_notify() MLME notification Disconnect(48) src/netdev.c:netdev_disconnect_event() src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is XX src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is DE src/wiphy.c:wiphy_radio_work_done() Work item 14 done src/station.c:station_connect_cb() 4, result: 4 Segmentation fault
2021-04-28 00:33:37 +02:00
/*
* If we failed due to the netdev being brought down,
* just abort the connection and do not try to send a
* CMD_DISCONNECT
*/
if (err == -ENETDOWN) {
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
msg = netdev_build_cmd_disconnect(netdev,
MMPDU_REASON_CODE_UNSPECIFIED);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg,
netdev_disconnect_cb,
netdev, NULL);
break;
case NL80211_IFTYPE_AP:
netdev: Detect netdev going down early In case the netdev is brought down while we're trying to connect, try to detect this and fail early instead of trying to send additional commands. src/station.c:station_enter_state() Old State: disconnected, new state: connecting src/station.c:station_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification Connect(46) src/netdev.c:netdev_connect_event() src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_1_of_4() ifindex=4 src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_3_of_4() ifindex=4 src/netdev.c:netdev_set_gtk() 4 src/station.c:station_handshake_event() Setting keys src/netdev.c:netdev_set_tk() 4 src/netdev.c:netdev_set_rekey_offload() 4 New Key for Group Key failed for ifindex: 4:Network is down src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/station.c:station_free() src/netdev.c:netdev_mlme_notify() MLME notification Disconnect(48) src/netdev.c:netdev_disconnect_event() src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is XX src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is DE src/wiphy.c:wiphy_radio_work_done() Work item 14 done src/station.c:station_connect_cb() 4, result: 4 Segmentation fault
2021-04-28 00:33:37 +02:00
if (err == -ENETDOWN)
return;
msg = netdev_build_cmd_del_station(netdev, nhs->super.spa,
MMPDU_REASON_CODE_UNSPECIFIED, false);
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
l_error("error sending DEL_STATION");
netdev: Detect netdev going down early In case the netdev is brought down while we're trying to connect, try to detect this and fail early instead of trying to send additional commands. src/station.c:station_enter_state() Old State: disconnected, new state: connecting src/station.c:station_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification Connect(46) src/netdev.c:netdev_connect_event() src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_1_of_4() ifindex=4 src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_3_of_4() ifindex=4 src/netdev.c:netdev_set_gtk() 4 src/station.c:station_handshake_event() Setting keys src/netdev.c:netdev_set_tk() 4 src/netdev.c:netdev_set_rekey_offload() 4 New Key for Group Key failed for ifindex: 4:Network is down src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/station.c:station_free() src/netdev.c:netdev_mlme_notify() MLME notification Disconnect(48) src/netdev.c:netdev_disconnect_event() src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is XX src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is DE src/wiphy.c:wiphy_radio_work_done() Work item 14 done src/station.c:station_connect_cb() 4, result: 4 Segmentation fault
2021-04-28 00:33:37 +02:00
break;
}
netdev: Detect netdev going down early In case the netdev is brought down while we're trying to connect, try to detect this and fail early instead of trying to send additional commands. src/station.c:station_enter_state() Old State: disconnected, new state: connecting src/station.c:station_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification Connect(46) src/netdev.c:netdev_connect_event() src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_1_of_4() ifindex=4 src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/eapol.c:eapol_handle_ptk_3_of_4() ifindex=4 src/netdev.c:netdev_set_gtk() 4 src/station.c:station_handshake_event() Setting keys src/netdev.c:netdev_set_tk() 4 src/netdev.c:netdev_set_rekey_offload() 4 New Key for Group Key failed for ifindex: 4:Network is down src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/station.c:station_free() src/netdev.c:netdev_mlme_notify() MLME notification Disconnect(48) src/netdev.c:netdev_disconnect_event() src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is XX src/netdev.c:netdev_link_notify() event 16 on ifindex 4 src/wiphy.c:wiphy_reg_notify() Notification of command Reg Change(36) src/wiphy.c:wiphy_update_reg_domain() New reg domain country code for (global) is DE src/wiphy.c:wiphy_radio_work_done() Work item 14 done src/station.c:station_connect_cb() 4, result: 4 Segmentation fault
2021-04-28 00:33:37 +02:00
netdev->result = NETDEV_RESULT_KEY_SETTING_FAILED;
handshake_event(&nhs->super, HANDSHAKE_EVENT_SETTING_KEYS_FAILED, &err);
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
static void try_handshake_complete(struct netdev_handshake_state *nhs)
{
if (nhs->ptk_installed && nhs->gtk_installed && nhs->igtk_installed &&
!nhs->complete) {
nhs->complete = true;
handshake_event(&nhs->super, HANDSHAKE_EVENT_COMPLETE);
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
if (nhs->netdev->type == NL80211_IFTYPE_STATION ||
nhs->netdev->type == NL80211_IFTYPE_P2P_CLIENT)
netdev_connect_ok(nhs->netdev);
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
}
}
static void netdev_set_station_cb(struct l_genl_msg *msg, void *user_data)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs = user_data;
struct netdev *netdev = nhs->netdev;
int err;
2018-06-22 02:32:55 +02:00
nhs->set_station_cmd_id = 0;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
nhs->ptk_installed = true;
if (netdev->type == NL80211_IFTYPE_STATION && !netdev->connected)
return;
err = l_genl_msg_get_error(msg);
if (err == -EOPNOTSUPP || err == -ENOTSUPP)
goto done;
if (err < 0) {
const char *ext_error = l_genl_msg_get_extended_error(msg);
l_error("Set Station failed for ifindex %d:%s", netdev->index,
ext_error ? ext_error : strerror(-err));
netdev_setting_keys_failed(nhs, err);
return;
}
done:
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
try_handshake_complete(nhs);
}
static void netdev_new_group_key_cb(struct l_genl_msg *msg, void *data)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs = data;
struct netdev *netdev = nhs->netdev;
int err = l_genl_msg_get_error(msg);
2018-06-22 02:32:55 +02:00
nhs->group_new_key_cmd_id = 0;
if (err < 0) {
const char *ext_error = l_genl_msg_get_extended_error(msg);
l_error("New Key for Group Key failed for ifindex: %d:%s",
netdev->index,
ext_error ? ext_error : strerror(-err));
netdev_setting_keys_failed(nhs, err);
return;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
nhs->gtk_installed = true;
try_handshake_complete(nhs);
}
2016-10-28 23:49:54 +02:00
static void netdev_new_group_management_key_cb(struct l_genl_msg *msg,
void *data)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs = data;
struct netdev *netdev = nhs->netdev;
int err = l_genl_msg_get_error(msg);
2016-10-28 23:49:54 +02:00
2018-06-22 02:32:55 +02:00
nhs->group_management_new_key_cmd_id = 0;
2016-10-28 23:49:54 +02:00
if (err < 0) {
const char *ext_error = l_genl_msg_get_extended_error(msg);
l_error("New Key for Group Mgmt failed for ifindex: %d:%s",
netdev->index,
ext_error ? ext_error : strerror(-err));
netdev_setting_keys_failed(nhs, err);
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
return;
2016-10-28 23:49:54 +02:00
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
nhs->igtk_installed = true;
try_handshake_complete(nhs);
2016-10-28 23:49:54 +02:00
}
static bool netdev_copy_tk(uint8_t *tk_buf, const uint8_t *tk,
uint32_t cipher, bool authenticator)
{
switch (cipher) {
case CRYPTO_CIPHER_CCMP:
/*
* 802.11-2016 12.8.3 Mapping PTK to CCMP keys:
* "A STA shall use the temporal key as the CCMP key
* for MPDUs between the two communicating STAs."
*/
memcpy(tk_buf, tk, 16);
break;
case CRYPTO_CIPHER_TKIP:
/*
* 802.11-2016 12.8.1 Mapping PTK to TKIP keys:
* "A STA shall use bits 0-127 of the temporal key as its
* input to the TKIP Phase 1 and Phase 2 mixing functions.
*
* A STA shall use bits 128-191 of the temporal key as
* the michael key for MSDUs from the Authenticator's STA
* to the Supplicant's STA.
*
* A STA shall use bits 192-255 of the temporal key as
* the michael key for MSDUs from the Supplicant's STA
* to the Authenticator's STA."
*/
if (authenticator) {
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_ENCR_KEY,
tk, 16);
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY,
tk + 16, 8);
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
tk + 24, 8);
} else {
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_ENCR_KEY,
tk, 16);
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
tk + 16, 8);
memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY,
tk + 24, 8);
}
break;
default:
l_error("Unexpected cipher: %x", cipher);
return false;
}
return true;
}
static const uint8_t *netdev_choose_key_address(
struct netdev_handshake_state *nhs)
{
return (nhs->super.authenticator) ? nhs->super.spa : nhs->super.aa;
}
static void netdev_set_gtk(struct handshake_state *hs, uint16_t key_index,
const uint8_t *gtk, uint8_t gtk_len,
const uint8_t *rsc, uint8_t rsc_len,
2018-06-22 02:32:55 +02:00
uint32_t cipher)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs =
2019-04-03 18:47:09 +02:00
l_container_of(hs, struct netdev_handshake_state, super);
2018-06-22 02:32:55 +02:00
struct netdev *netdev = nhs->netdev;
uint8_t gtk_buf[32];
struct l_genl_msg *msg;
const uint8_t *addr = (netdev->type == NL80211_IFTYPE_ADHOC) ?
nhs->super.aa : NULL;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
nhs->gtk_installed = false;
l_debug("%d", netdev->index);
if (crypto_cipher_key_len(cipher) != gtk_len) {
l_error("Unexpected key length: %d", gtk_len);
netdev_setting_keys_failed(nhs, -ERANGE);
return;
}
if (!netdev_copy_tk(gtk_buf, gtk, cipher, false)) {
netdev_setting_keys_failed(nhs, -ENOENT);
return;
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
if (hs->wait_for_gtk) {
l_timeout_remove(netdev->group_handshake_timeout);
netdev->group_handshake_timeout = NULL;
}
msg = nl80211_build_new_key_group(netdev->index, cipher, key_index,
gtk_buf, gtk_len, rsc, rsc_len, addr);
2018-06-22 02:32:55 +02:00
nhs->group_new_key_cmd_id =
l_genl_family_send(nl80211, msg, netdev_new_group_key_cb,
2018-06-22 02:32:55 +02:00
nhs, NULL);
2018-06-22 02:32:55 +02:00
if (nhs->group_new_key_cmd_id > 0)
return;
l_genl_msg_unref(msg);
netdev_setting_keys_failed(nhs, -EIO);
}
static void netdev_set_igtk(struct handshake_state *hs, uint16_t key_index,
2016-10-28 23:49:54 +02:00
const uint8_t *igtk, uint8_t igtk_len,
const uint8_t *ipn, uint8_t ipn_len,
2018-06-22 02:32:55 +02:00
uint32_t cipher)
2016-10-28 23:49:54 +02:00
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs =
2019-04-03 18:47:09 +02:00
l_container_of(hs, struct netdev_handshake_state, super);
2016-10-28 23:49:54 +02:00
uint8_t igtk_buf[16];
2018-06-22 02:32:55 +02:00
struct netdev *netdev = nhs->netdev;
2016-10-28 23:49:54 +02:00
struct l_genl_msg *msg;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
nhs->igtk_installed = false;
2016-10-28 23:49:54 +02:00
l_debug("%d", netdev->index);
if (crypto_cipher_key_len(cipher) != igtk_len) {
l_error("Unexpected key length: %d", igtk_len);
netdev_setting_keys_failed(nhs, -ERANGE);
return;
}
2016-10-28 23:49:54 +02:00
switch (cipher) {
case CRYPTO_CIPHER_BIP:
memcpy(igtk_buf, igtk, 16);
break;
default:
l_error("Unexpected cipher: %x", cipher);
netdev_setting_keys_failed(nhs, -ENOENT);
2016-10-28 23:49:54 +02:00
return;
}
if (key_index == 0x0400 || key_index == 0x0500) {
l_warn("Received an invalid IGTK key index (%04hx)"
" that is likely in"
" big endian format. Trying to fix and"
" proceed anyway", key_index);
key_index = bswap_16(key_index);
}
msg = nl80211_build_new_key_group(netdev->index, cipher, key_index,
igtk_buf, igtk_len, ipn, ipn_len, NULL);
2018-06-22 02:32:55 +02:00
nhs->group_management_new_key_cmd_id =
2016-10-28 23:49:54 +02:00
l_genl_family_send(nl80211, msg,
netdev_new_group_management_key_cb,
2018-06-22 02:32:55 +02:00
nhs, NULL);
2016-10-28 23:49:54 +02:00
2018-06-22 02:32:55 +02:00
if (nhs->group_management_new_key_cmd_id > 0)
2016-10-28 23:49:54 +02:00
return;
l_genl_msg_unref(msg);
netdev_setting_keys_failed(nhs, -EIO);
2016-10-28 23:49:54 +02:00
}
static void netdev_new_pairwise_key_cb(struct l_genl_msg *msg, void *data)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs = data;
struct netdev *netdev = nhs->netdev;
const uint8_t *addr = netdev_choose_key_address(nhs);
int err = l_genl_msg_get_error(msg);
2018-06-22 02:32:55 +02:00
nhs->pairwise_new_key_cmd_id = 0;
if (err < 0) {
const char *ext_error = l_genl_msg_get_extended_error(msg);
l_error("New Key for Pairwise Key failed for ifindex: %d:%s",
netdev->index,
ext_error ? ext_error : strerror(-err));
goto error;
}
/*
* Set the AUTHORIZED flag using a SET_STATION command even if
* we're already operational, it will not hurt during re-keying
* and is necessary after an FT.
*/
msg = nl80211_build_set_station_authorized(netdev->index, addr);
2018-06-22 02:32:55 +02:00
nhs->set_station_cmd_id =
l_genl_family_send(nl80211, msg, netdev_set_station_cb,
2018-06-22 02:32:55 +02:00
nhs, NULL);
if (nhs->set_station_cmd_id > 0)
return;
l_genl_msg_unref(msg);
err = -EIO;
error:
netdev_setting_keys_failed(nhs, err);
}
static struct l_genl_msg *netdev_build_cmd_new_key_pairwise(
struct netdev *netdev,
uint32_t cipher,
const uint8_t *aa,
const uint8_t *tk,
size_t tk_len)
{
uint8_t key_id = 0;
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_NEW_KEY, 512);
l_genl_msg_append_attr(msg, NL80211_ATTR_KEY_DATA, tk_len, tk);
l_genl_msg_append_attr(msg, NL80211_ATTR_KEY_CIPHER, 4, &cipher);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, aa);
l_genl_msg_append_attr(msg, NL80211_ATTR_KEY_IDX, 1, &key_id);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
return msg;
}
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
static void netdev_group_timeout_cb(struct l_timeout *timeout, void *user_data)
{
struct netdev_handshake_state *nhs = user_data;
/*
* There was a problem with the ptk, this should have triggered a key
* setting failure event already.
*/
if (!nhs->ptk_installed)
return;
/*
* If this happens, we never completed the group handshake. We can still
* complete the connection, but we will not have group traffic.
*/
l_warn("completing connection with no group traffic on ifindex %d",
nhs->netdev->index);
nhs->complete = true;
handshake_event(&nhs->super, HANDSHAKE_EVENT_COMPLETE);
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
netdev_connect_ok(nhs->netdev);
}
2018-06-22 02:32:55 +02:00
static void netdev_set_tk(struct handshake_state *hs,
const uint8_t *tk, uint32_t cipher)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs =
2019-04-03 18:47:09 +02:00
l_container_of(hs, struct netdev_handshake_state, super);
uint8_t tk_buf[32];
2018-06-22 02:32:55 +02:00
struct netdev *netdev = nhs->netdev;
struct l_genl_msg *msg;
const uint8_t *addr = netdev_choose_key_address(nhs);
int err;
netdev: signal handshake complete after setting all keys Currently, netdev triggers the HANDSHAKE_COMPLETE event after completing the SET_STATION (after setting the pairwise key). Depending on the timing this may happen before the GTK/IGTK are set which will result in group traffic not working initially (the GTK/IGTK would still get set, but group traffic would not work immediately after DBus said you were connected, this mainly poses a problem with autotests). In order to fix this, several flags were added in netdev_handshake_state: ptk_installed, gtk_installed, igtk_installed, and completed. Each of these flags are set true when their respective keys are set, and in each key callback we try to trigger the handshake complete event (assuming all the flags are true). Initially the gtk/igtk flags are set to true, for reasons explained below. In the WPA2 case, all the key setter functions are called sequentially from eapol. With this change, the PTK is now set AFTER the gtk/igtk. This is because the gtk/igtk are optional and only set if group traffic is allowed. If the gtk/igtk are not used, we set the PTK and can immediately trigger the handshake complete event (since gtk_installed/igtk_installed are initialized as true). When the gtk/igtk are being set, we immediately set their flags to false and wait for their callbacks in addition to the PTK callback. Doing it this way handles both group traffic and non group traffic paths. WPA1 throws a wrench into this since the group keys are obtained in a separate handshake. For this case a new flag was added to the handshake_state, 'wait_for_gtk'. This allows netdev to set the PTK after the initial 4-way, but still wait for the gtk/igtk setters to get called before triggering the handshake complete event. As a precaution, netdev sets a timeout that will trigger if the gtk/igtk setters are never called. In this case we can still complete the connection, but print a warning that group traffic will not be allowed.
2018-10-26 18:44:58 +02:00
/*
* WPA1 does the group handshake after the 4-way finishes so we can't
* rely on the gtk/igtk being set immediately after the ptk. Since
* 'gtk_installed' is initially set to true (to handle NO_GROUP_TRAFFIC)
* we must set it false so we don't notify that the connection was
* successful until we get the gtk/igtk callbacks. Note that we do not
* need to set igtk_installed false because the igtk could not happen at
* all.
*/
if (hs->wait_for_gtk) {
nhs->gtk_installed = false;
netdev->group_handshake_timeout = l_timeout_create(2,
netdev_group_timeout_cb, nhs, NULL);
}
/*
* 802.11 Section 4.10.4.3:
* Because in an IBSS there are two 4-way handshakes between
* any two Supplicants and Authenticators, the pairwise key used
* between any two STAs is from the 4-way handshake initiated
* by the STA Authenticator with the higher MAC address...
*/
if (netdev->type == NL80211_IFTYPE_ADHOC &&
memcmp(nhs->super.aa, nhs->super.spa, 6) < 0) {
nhs->ptk_installed = true;
try_handshake_complete(nhs);
return;
}
l_debug("%d", netdev->index);
err = -ENOENT;
if (!netdev_copy_tk(tk_buf, tk, cipher, false))
goto invalid_key;
msg = netdev_build_cmd_new_key_pairwise(netdev, cipher, addr, tk_buf,
crypto_cipher_key_len(cipher));
2018-06-22 02:32:55 +02:00
nhs->pairwise_new_key_cmd_id =
l_genl_family_send(nl80211, msg, netdev_new_pairwise_key_cb,
2018-06-22 02:32:55 +02:00
nhs, NULL);
if (nhs->pairwise_new_key_cmd_id > 0)
return;
err = -EIO;
l_genl_msg_unref(msg);
invalid_key:
netdev_setting_keys_failed(nhs, err);
}
static void netdev_set_pmk_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev_handshake_state *nhs = user_data;
struct netdev *netdev = nhs->netdev;
int err = l_genl_msg_get_error(msg);
nhs->set_pmk_cmd_id = 0;
if (err < 0) {
l_error("Error with SET_PMK/SET_STATION");
netdev_setting_keys_failed(nhs, err);
return;
}
handshake_event(netdev->handshake, HANDSHAKE_EVENT_SETTING_KEYS);
netdev_connect_ok(netdev);
}
static void netdev_set_pmk(struct handshake_state *hs, const uint8_t *pmk,
size_t pmk_len)
{
struct l_genl_msg *msg;
struct netdev_handshake_state *nhs = l_container_of(hs,
struct netdev_handshake_state, super);
struct netdev *netdev = nhs->netdev;
/* Only relevent for 8021x offload */
if (nhs->type != CONNECTION_TYPE_8021X_OFFLOAD)
return;
msg = l_genl_msg_new(NL80211_CMD_SET_PMK);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, 6, netdev->handshake->aa);
l_genl_msg_append_attr(msg, NL80211_ATTR_PMK,
netdev->handshake->pmk_len,
netdev->handshake->pmk);
nhs->set_pmk_cmd_id = l_genl_family_send(nl80211, msg,
netdev_set_pmk_cb,
nhs, NULL);
if (!nhs->set_pmk_cmd_id) {
l_error("Failed to set SET_PMK");
netdev_setting_keys_failed(nhs, -EIO);
return;
}
}
void netdev_handshake_failed(struct handshake_state *hs, uint16_t reason_code)
{
struct netdev_handshake_state *nhs =
2019-04-03 18:47:09 +02:00
l_container_of(hs, struct netdev_handshake_state, super);
struct netdev *netdev = nhs->netdev;
struct l_genl_msg *msg;
l_error("4-Way handshake failed for ifindex: %d, reason: %u",
netdev->index, reason_code);
netdev->sm = NULL;
netdev->result = NETDEV_RESULT_HANDSHAKE_FAILED;
netdev->last_code = reason_code;
switch (netdev->type) {
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
msg = netdev_build_cmd_disconnect(netdev, reason_code);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg,
netdev_disconnect_cb,
netdev, NULL);
break;
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
msg = netdev_build_cmd_del_station(netdev, nhs->super.spa,
reason_code, false);
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
l_error("error sending DEL_STATION");
}
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
static void hardware_rekey_cb(struct l_genl_msg *msg, void *data)
{
struct netdev *netdev = data;
int err;
netdev->rekey_offload_cmd_id = 0;
err = l_genl_msg_get_error(msg);
if (err < 0) {
if (err == -EOPNOTSUPP) {
l_error("hardware_rekey not supported");
netdev->rekey_offload_support = false;
}
/*
* TODO: Ignore all other errors for now, until WoWLAN is
* supported properly
*/
}
}
static struct l_genl_msg *netdev_build_cmd_replay_counter(struct netdev *netdev,
2018-11-19 19:09:27 +01:00
const uint8_t *kek,
const uint8_t *kck,
uint64_t replay_ctr)
{
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_SET_REKEY_OFFLOAD, 512);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_enter_nested(msg, NL80211_ATTR_REKEY_DATA);
l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_KEK,
NL80211_KEK_LEN, kek);
l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_KCK,
NL80211_KCK_LEN, kck);
l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_REPLAY_CTR,
NL80211_REPLAY_CTR_LEN, &replay_ctr);
l_genl_msg_leave_nested(msg);
return msg;
}
static void netdev_set_rekey_offload(uint32_t ifindex,
const uint8_t *kek,
const uint8_t *kck,
uint64_t replay_counter,
void *user_data)
{
struct netdev *netdev;
struct l_genl_msg *msg;
netdev = netdev_find(ifindex);
if (!netdev)
return;
if (netdev->type != NL80211_IFTYPE_STATION)
return;
if (!netdev->rekey_offload_support)
return;
l_debug("%d", netdev->index);
msg = netdev_build_cmd_replay_counter(netdev, kek, kck, replay_counter);
netdev->rekey_offload_cmd_id = l_genl_family_send(nl80211, msg,
hardware_rekey_cb,
netdev, NULL);
}
static void netdev_qos_map_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
int err = l_genl_msg_get_error(msg);
const char *ext_error;
netdev->qos_map_cmd_id = 0;
if (err >= 0)
return;
ext_error = l_genl_msg_get_extended_error(msg);
l_error("Couuld not set QoS Map in kernel: %s",
ext_error ? ext_error : strerror(-err));
}
/*
* TODO: Fix this in the kernel:
*
* The QoS Map is really of no use to IWD. The kernel requires it to map QoS
* network values properly to what it puts into the IP header. The way we have
* to let the kernel know is to receive the IE, then give it right back...
*
* The kernel/driver/firmware *could* simply obtain this information as the
2020-01-21 07:21:38 +01:00
* frame comes in and not require userspace to forward it back... but that's a
* battle for another day.
*/
static void netdev_send_qos_map_set(struct netdev *netdev,
const uint8_t *qos_set, size_t qos_len)
{
struct l_genl_msg *msg;
if (!wiphy_supports_qos_set_map(netdev->wiphy)) {
l_warn("AP sent QoS Map, but capability was not advertised!");
return;
}
/*
* Since this IE comes in on either a management frame or during
* Association response we could have potentially already set this.
*/
if (netdev->qos_map_cmd_id)
return;
msg = l_genl_msg_new_sized(NL80211_CMD_SET_QOS_MAP, 128 + qos_len);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_QOS_MAP, qos_len, qos_set);
netdev->qos_map_cmd_id = l_genl_family_send(nl80211, msg,
netdev_qos_map_cb,
netdev, NULL);
}
static void parse_request_ies(struct netdev *netdev, const uint8_t *ies,
size_t ies_len)
{
struct ie_tlv_iter iter;
const void *data;
const uint8_t *rsnxe = NULL;
/*
* The driver may have modified the IEs we passed to CMD_CONNECT
* before sending them out, the actual IE sent is reflected in the
* ATTR_REQ_IE sequence. These are the values EAPoL will need to use.
*/
ie_tlv_iter_init(&iter, ies, ies_len);
while (ie_tlv_iter_next(&iter)) {
data = ie_tlv_iter_get_data(&iter);
switch (ie_tlv_iter_get_tag(&iter)) {
case IE_TYPE_RSN:
handshake_state_set_supplicant_ie(netdev->handshake,
data - 2);
break;
case IE_TYPE_RSNX:
if (!rsnxe)
rsnxe = data - 2;
break;
case IE_TYPE_VENDOR_SPECIFIC:
if (!is_ie_wpa_ie(data, ie_tlv_iter_get_length(&iter)))
break;
handshake_state_set_supplicant_ie(netdev->handshake,
data - 2);
break;
case IE_TYPE_MOBILITY_DOMAIN:
handshake_state_set_mde(netdev->handshake, data - 2);
break;
}
}
/* RSNXE element might be omitted when FTing */
handshake_state_set_supplicant_rsnxe(netdev->handshake, rsnxe);
}
static void netdev_driver_connected(struct netdev *netdev)
{
netdev->connected = true;
if (netdev->event_filter)
netdev->event_filter(netdev, NETDEV_EVENT_ASSOCIATING, NULL,
netdev->user_data);
/*
* We register the eapol state machine here, in case the PAE
* socket receives EAPoL packets before the nl80211 socket
* receives the connected event. The logical sequence of
* events can be reversed (e.g. connect_event, then PAE data)
* due to scheduling
*/
if (netdev->sm)
eapol_register(netdev->sm);
}
2018-11-19 19:09:27 +01:00
static void netdev_connect_event(struct l_genl_msg *msg, struct netdev *netdev)
{
2016-06-27 23:35:31 +02:00
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const uint16_t *status_code = NULL;
const uint8_t *ies = NULL;
size_t ies_len = 0;
struct ie_tlv_iter iter;
const uint8_t *resp_ies = NULL;
size_t resp_ies_len;
l_debug("");
if (netdev->aborting)
return;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
if (netdev->ignore_connect_event)
return;
/* Work around mwifiex which sends a Connect Event prior to the Ack */
if (netdev->connect_cmd_id)
netdev_driver_connected(netdev);
if (!netdev->connected) {
l_warn("Unexpected connection related event -- "
"is another supplicant running?");
return;
}
2016-06-27 23:35:31 +02:00
if (!l_genl_attr_init(&attr, msg)) {
l_debug("attr init failed");
goto error;
}
2016-06-27 23:35:31 +02:00
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_TIMED_OUT:
l_warn("authentication timed out");
goto error;
case NL80211_ATTR_STATUS_CODE:
if (len == sizeof(uint16_t))
status_code = data;
break;
case NL80211_ATTR_REQ_IE:
ies = data;
ies_len = len;
break;
case NL80211_ATTR_RESP_IE:
resp_ies = data;
resp_ies_len = len;
break;
2016-06-27 23:35:31 +02:00
}
}
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
if (netdev->expect_connect_failure) {
/*
* The kernel may think we are connected when we are actually
* expecting a failure here, e.g. if Authenticate/Associate had
* previously failed. If so we need to deauth to let the kernel
* know.
*/
if (status_code && *status_code == 0)
goto deauth;
else
goto error;
}
/* AP Rejected the authenticate / associate */
if (!status_code || *status_code != 0)
goto error;
if (!ies)
goto process_resp_ies;
parse_request_ies(netdev, ies, ies_len);
process_resp_ies:
if (resp_ies) {
const uint8_t *fte = NULL;
const uint8_t *qos_set = NULL;
size_t qos_len = 0;
struct ie_ft_info ft_info;
ie_tlv_iter_init(&iter, resp_ies, resp_ies_len);
while (ie_tlv_iter_next(&iter)) {
data = ie_tlv_iter_get_data(&iter);
switch (ie_tlv_iter_get_tag(&iter)) {
case IE_TYPE_FAST_BSS_TRANSITION:
fte = data - 2;
break;
case IE_TYPE_QOS_MAP_SET:
qos_set = data;
qos_len = ie_tlv_iter_get_length(&iter);
break;
}
}
/* FILS handles its own FT key derivation */
if (fte && !(netdev->handshake->akm_suite &
(IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384))) {
uint32_t kck_len =
handshake_state_get_kck_len(netdev->handshake);
/*
* If we are here, then most likely we have a FullMac
* hw performing initial mobility association. We need
* to set the FTE element or the handshake will fail
* The firmware accepted the FTE element, so do not
* sanitize the contents and just assume they're okay.
*/
if (ie_parse_fast_bss_transition_from_data(fte,
fte[1] + 2, kck_len, &ft_info) >= 0) {
handshake_state_set_fte(netdev->handshake, fte);
handshake_state_set_kh_ids(netdev->handshake,
ft_info.r0khid,
ft_info.r0khid_len,
ft_info.r1khid);
} else {
l_info("CMD_CONNECT Succeeded, but parsing FTE"
" failed. Expect handshake failure");
}
}
if (qos_set)
netdev_send_qos_map_set(netdev, qos_set, qos_len);
}
if (netdev->sm) {
/*
* Start processing EAPoL frames now that the state machine
* has all the input data even in FT mode.
*/
if (!eapol_start(netdev->sm))
goto error;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
return;
}
/* Allow station to sync the PSK to disk */
if (is_offload(netdev->handshake))
handshake_event(netdev->handshake,
HANDSHAKE_EVENT_SETTING_KEYS);
netdev_connect_ok(netdev);
return;
error:
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
(status_code) ? *status_code :
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
deauth:
msg = netdev_build_cmd_deauthenticate(netdev,
MMPDU_REASON_CODE_UNSPECIFIED);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211,
msg,
netdev_disconnect_cb,
netdev, NULL);
}
static unsigned int ie_rsn_akm_suite_to_nl80211(enum ie_rsn_akm_suite akm)
2016-06-27 23:35:31 +02:00
{
switch (akm) {
case IE_RSN_AKM_SUITE_8021X:
return CRYPTO_AKM_8021X;
case IE_RSN_AKM_SUITE_PSK:
return CRYPTO_AKM_PSK;
case IE_RSN_AKM_SUITE_FT_OVER_8021X:
return CRYPTO_AKM_FT_OVER_8021X;
case IE_RSN_AKM_SUITE_FT_USING_PSK:
return CRYPTO_AKM_FT_USING_PSK;
case IE_RSN_AKM_SUITE_8021X_SHA256:
return CRYPTO_AKM_8021X_SHA256;
case IE_RSN_AKM_SUITE_PSK_SHA256:
return CRYPTO_AKM_PSK_SHA256;
case IE_RSN_AKM_SUITE_TDLS:
return CRYPTO_AKM_TDLS;
case IE_RSN_AKM_SUITE_SAE_SHA256:
return CRYPTO_AKM_SAE_SHA256;
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
return CRYPTO_AKM_FT_OVER_SAE_SHA256;
case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256:
return CRYPTO_AKM_AP_PEER_KEY_SHA256;
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256:
return CRYPTO_AKM_8021X_SUITE_B_SHA256;
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384:
return CRYPTO_AKM_8021X_SUITE_B_SHA384;
case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384:
return CRYPTO_AKM_FT_OVER_8021X_SHA384;
case IE_RSN_AKM_SUITE_FILS_SHA256:
return CRYPTO_AKM_FILS_SHA256;
case IE_RSN_AKM_SUITE_FILS_SHA384:
return CRYPTO_AKM_FILS_SHA384;
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
return CRYPTO_AKM_FT_OVER_FILS_SHA256;
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
return CRYPTO_AKM_FT_OVER_FILS_SHA384;
case IE_RSN_AKM_SUITE_OWE:
return CRYPTO_AKM_OWE;
2019-06-07 22:58:43 +02:00
case IE_RSN_AKM_SUITE_OSEN:
return CRYPTO_AKM_OSEN;
}
return 0;
}
static struct l_genl_msg *netdev_build_cmd_associate_common(
struct netdev *netdev)
{
struct handshake_state *hs = netdev->handshake;
bool is_rsn = hs->supplicant_ie != NULL;
struct l_genl_msg *msg;
2016-06-27 23:35:31 +02:00
msg = l_genl_msg_new_sized(NL80211_CMD_ASSOCIATE, 600);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4,
&netdev->frequency);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, hs->aa);
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, hs->ssid_len, hs->ssid);
l_genl_msg_append_attr(msg, NL80211_ATTR_SOCKET_OWNER, 0, NULL);
if (is_rsn) {
uint32_t nl_cipher;
uint32_t nl_akm;
uint32_t wpa_version;
l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT, 0, NULL);
if (netdev->pae_over_nl80211)
l_genl_msg_append_attr(msg,
NL80211_ATTR_CONTROL_PORT_OVER_NL80211,
0, NULL);
if (hs->pairwise_cipher == IE_RSN_CIPHER_SUITE_CCMP)
nl_cipher = CRYPTO_CIPHER_CCMP;
else
nl_cipher = CRYPTO_CIPHER_TKIP;
l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE,
4, &nl_cipher);
if (hs->group_cipher == IE_RSN_CIPHER_SUITE_CCMP)
nl_cipher = CRYPTO_CIPHER_CCMP;
else
nl_cipher = CRYPTO_CIPHER_TKIP;
l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITE_GROUP,
4, &nl_cipher);
if (hs->mfp) {
uint32_t use_mfp = NL80211_MFP_REQUIRED;
l_genl_msg_append_attr(msg, NL80211_ATTR_USE_MFP,
4, &use_mfp);
}
nl_akm = ie_rsn_akm_suite_to_nl80211(hs->akm_suite);
if (nl_akm)
l_genl_msg_append_attr(msg, NL80211_ATTR_AKM_SUITES,
4, &nl_akm);
if (hs->wpa_ie)
wpa_version = NL80211_WPA_VERSION_1;
else
wpa_version = NL80211_WPA_VERSION_2;
l_genl_msg_append_attr(msg, NL80211_ATTR_WPA_VERSIONS,
4, &wpa_version);
}
return msg;
}
static void netdev_cmd_ft_reassociate_cb(struct l_genl_msg *msg,
void *user_data)
{
struct netdev *netdev = user_data;
netdev: Cancel the CMD_CONNECT genl command on disconnect CMD_DISCONNECT fails on some occasions when CMD_CONNECT is still running. When this happens the DBus disconnect command receives an error reply but iwd's device state is left as disconnected even though there's a connection at the kernel level which times out a few seconds later. If the CMD_CONNECT is cancelled I couldn't reproduce this so far. src/network.c:network_connect() src/network.c:network_connect_psk() src/network.c:network_connect_psk() psk: 69ae3f8b2f84a438cf6a44275913182dd2714510ccb8cbdf8da9dc8b61718560 src/network.c:network_connect_psk() len: 32 src/network.c:network_connect_psk() ask_psk: false src/device.c:device_enter_state() Old State: disconnected, new state: connecting src/scan.c:scan_notify() Scan notification 33 src/device.c:device_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/device.c:device_dbus_disconnect() src/device.c:device_connect_cb() 6, result: 5 src/device.c:device_enter_state() Old State: connecting, new state: disconnecting src/device.c:device_disconnect_cb() 6, success: 0 src/device.c:device_enter_state() Old State: disconnecting, new state: disconnected src/scan.c:scan_notify() Scan notification 34 src/netdev.c:netdev_mlme_notify() MLME notification 19 src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 37 src/netdev.c:netdev_authenticate_event() src/scan.c:get_scan_callback() get_scan_callback src/scan.c:get_scan_done() get_scan_done src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 19 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 38 src/netdev.c:netdev_associate_event() src/netdev.c:netdev_mlme_notify() MLME notification 46 src/netdev.c:netdev_connect_event() <delay> src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 src/netdev.c:netdev_mlme_notify() MLME notification 39 src/netdev.c:netdev_deauthenticate_event()
2016-08-05 14:25:34 +02:00
netdev->connect_cmd_id = 0;
if (l_genl_msg_get_error(msg) < 0) {
struct l_genl_msg *cmd_deauth;
netdev->result = NETDEV_RESULT_ASSOCIATION_FAILED;
netdev->last_code = MMPDU_STATUS_CODE_UNSPECIFIED;
cmd_deauth = netdev_build_cmd_deauthenticate(netdev,
MMPDU_REASON_CODE_UNSPECIFIED);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211,
cmd_deauth,
netdev_disconnect_cb,
netdev, NULL);
}
}
2016-09-23 00:34:27 +02:00
static void netdev_authenticate_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const uint8_t *frame = NULL;
size_t frame_len = 0;
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
l_debug("");
if (netdev->aborting)
return;
if (!netdev->connected) {
l_warn("Unexpected connection related event -- "
"is another supplicant running?");
return;
}
/*
* During Fast Transition we use the authenticate event to start the
* reassociation step because the FTE necessary before we can build
* the FT Associate command is included in the attached frame and is
* not available in the Authenticate command callback.
*/
if (!netdev->ap)
return;
if (!l_genl_attr_init(&attr, msg)) {
l_debug("attr init failed");
goto auth_error;
}
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_TIMED_OUT:
l_warn("authentication timed out");
if (auth_proto_auth_timeout(netdev->ap))
return;
goto auth_error;
case NL80211_ATTR_FRAME:
if (frame)
goto auth_error;
frame = data;
frame_len = len;
break;
}
}
if (L_WARN_ON(!frame))
goto auth_error;
if (netdev->ap) {
const struct mmpdu_header *hdr;
const struct mmpdu_authentication *auth;
if (L_WARN_ON(!(hdr = mpdu_validate(frame, frame_len))))
goto auth_error;
auth = mmpdu_body(hdr);
status_code = L_CPU_TO_LE16(auth->status);
ret = auth_proto_rx_authenticate(netdev->ap, frame, frame_len);
if (ret == 0 || ret == -EAGAIN)
return;
else if (ret > 0)
status_code = (uint16_t)ret;
}
auth_error:
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
status_code);
}
static void netdev_associate_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
size_t frame_len = 0;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
const uint8_t *frame = NULL;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
int ret;
l_debug("");
if (!netdev->connected || netdev->aborting)
return;
if (!netdev->ap) {
netdev->associated = true;
return;
}
if (!l_genl_attr_init(&attr, msg)) {
l_debug("attr init failed");
return;
}
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_TIMED_OUT:
2018-11-19 18:53:30 +01:00
l_warn("association timed out");
if (auth_proto_assoc_timeout(netdev->ap))
return;
/*
* There will be no connect event when Associate times
* out. The failed connection must be explicitly
* initiated here.
*/
netdev_connect_failed(netdev,
NETDEV_RESULT_ASSOCIATION_FAILED,
status_code);
return;
case NL80211_ATTR_FRAME:
frame = data;
frame_len = len;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
break;
}
}
if (L_WARN_ON(!frame))
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
goto assoc_failed;
if (netdev->ap) {
const struct mmpdu_header *hdr;
const struct mmpdu_association_response *assoc;
if (L_WARN_ON(!(hdr = mpdu_validate(frame, frame_len))))
goto assoc_failed;
assoc = mmpdu_body(hdr);
status_code = L_CPU_TO_LE16(assoc->status_code);
ret = auth_proto_rx_associate(netdev->ap, frame, frame_len);
if (ret == 0) {
bool fils = !!(netdev->handshake->akm_suite &
(IE_RSN_AKM_SUITE_FILS_SHA256 |
IE_RSN_AKM_SUITE_FILS_SHA384 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256));
auth_proto_free(netdev->ap);
netdev->ap = NULL;
netdev->sm = eapol_sm_new(netdev->handshake);
eapol_register(netdev->sm);
/* Just in case this was a retry */
netdev->ignore_connect_event = false;
/*
* If in FT and/or FILS we don't force an initial 4-way
* handshake and instead just keep the EAPoL state
* machine for the rekeys.
*/
if (netdev->in_ft || fils)
eapol_sm_set_require_handshake(netdev->sm,
false);
netdev->in_ft = false;
netdev->in_reassoc = false;
netdev->associated = true;
return;
} else if (ret == -EAGAIN) {
/*
* Here to support OWE retries. OWE will retry
2021-04-05 20:44:02 +02:00
* internally, but a connect event will still be emitted
*/
netdev->ignore_connect_event = true;
return;
} else if (ret > 0)
status_code = (uint16_t)ret;
goto assoc_failed;
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
}
return;
assoc_failed:
netdev: process association in netdev_associate_event Apart from OWE, the association event was disregarded and all association processing was done in netdev_connect_event. This led to netdev_connect_event having to handle all the logic of both success and failure, as well as parsing the association for FT and OWE. Also, without checking the status code in the associate frame there is the potential for the kernel to think we are connected even if association failed (e.g. rogue AP). This change introduces two flags into netdev, expect_connect_failure and ignore_connect_event. All the FT processing that was once in netdev_connect_event has now been moved into netdev_associate_event, as well as non-FT associate frame processing. The connect event now only handles failure cases for soft/half MAC cards. Note: Since fullmac cards rely on the connect event, the eapol_start and netdev_connect_ok were left in netdev_connect_event. Since neither auth/assoc events come in on fullmac we shouldn't have any conflict with the new flags. Once a connection has completed association, EAPoL is started from netdev_associate_event (if required) and the ignore_connect_event flag can be set. This will bypass the connect event. If a connection has failed during association for whatever reason, we can set expect_connect_failure, the netdev reason, and the MPDU status code. This allows netdev_connect_event to both handle the error, and, if required, send a deauth telling the kernel that we have failed (protecting against the rogue AP situation).
2019-03-05 22:42:33 +01:00
netdev->result = NETDEV_RESULT_ASSOCIATION_FAILED;
netdev->last_code = status_code;
netdev->expect_connect_failure = true;
}
static void netdev_cmd_connect_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
netdev->connect_cmd_id = 0;
if (l_genl_msg_get_error(msg) >= 0) {
/*
* connected should be false if the connect event hasn't come
* in yet. i.e. the CMD_CONNECT ack arrived first (typical).
* Mark the connection as 'connected'
*/
if (!netdev->connected)
netdev_driver_connected(netdev);
return;
}
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
2018-11-19 19:09:27 +01:00
static struct l_genl_msg *netdev_build_cmd_authenticate(struct netdev *netdev,
uint32_t auth_type)
{
struct handshake_state *hs = netdev->handshake;
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_AUTHENTICATE, 512);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ,
4, &netdev->frequency);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN,
netdev->handshake->aa);
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, hs->ssid_len, hs->ssid);
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type);
return msg;
}
static void netdev_scan_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
if (l_genl_msg_get_error(msg) < 0) {
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
netdev->retry_auth = true;
}
static void netdev_auth_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
struct handshake_state *hs = netdev->handshake;
int err = l_genl_msg_get_error(msg);
struct l_genl_msg *scan_msg;
if (!err) {
l_genl_msg_unref(netdev->auth_cmd);
netdev->auth_cmd = NULL;
return;
}
l_debug("Error during auth: %d", err);
if (!netdev->auth_cmd || err != -ENOENT) {
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
/* Kernel can't find the BSS in its cache, scan and retry */
scan_msg = scan_build_trigger_scan_bss(netdev->index, netdev->wiphy,
netdev->frequency,
hs->ssid, hs->ssid_len);
if (!l_genl_family_send(nl80211, scan_msg,
netdev_scan_cb, netdev, NULL)) {
l_genl_msg_unref(scan_msg);
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_new_scan_results_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
if (!netdev->retry_auth)
return;
l_debug("");
if (!l_genl_family_send(nl80211, netdev->auth_cmd,
netdev_auth_cb, netdev, NULL)) {
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
netdev->auth_cmd = NULL;
}
static void netdev_assoc_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
if (l_genl_msg_get_error(msg) < 0) {
l_error("Error sending CMD_ASSOCIATE");
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_sae_tx_authenticate(const uint8_t *body,
size_t body_len, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_authenticate(netdev, NL80211_AUTHTYPE_SAE);
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_DATA, body_len, body);
if (!l_genl_family_send(nl80211, msg, netdev_auth_cb, netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
netdev->auth_cmd = l_genl_msg_ref(msg);
}
static void netdev_sae_tx_associate(void *user_data)
{
struct netdev *netdev = user_data;
struct handshake_state *hs = netdev->handshake;
struct l_genl_msg *msg;
struct iovec iov[64];
unsigned int n_iov = L_ARRAY_SIZE(iov);
unsigned int n_used = 0;
enum mpdu_management_subtype subtype =
MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST;
msg = netdev_build_cmd_associate_common(netdev);
n_used = iov_ie_append(iov, n_iov, n_used, hs->supplicant_ie);
n_used = iov_ie_append(iov, n_iov, n_used, hs->mde);
n_used = iov_ie_append(iov, n_iov, n_used, hs->supplicant_rsnxe);
n_used = netdev_populate_common_ies(netdev, hs, msg,
iov, n_iov, n_used);
mpdu_sort_ies(subtype, iov, n_used);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, n_used);
/* If doing a non-FT Reassociation */
if (netdev->in_reassoc)
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, 6,
netdev->ap->prev_bssid);
if (!l_genl_family_send(nl80211, msg, netdev_assoc_cb, netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_owe_tx_authenticate(void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_authenticate(netdev,
NL80211_AUTHTYPE_OPEN_SYSTEM);
if (!l_genl_family_send(nl80211, msg, netdev_auth_cb,
2018-11-19 19:09:27 +01:00
netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
netdev->auth_cmd = l_genl_msg_ref(msg);
}
static void netdev_owe_tx_associate(struct iovec *owe_iov, size_t n_owe_iov,
void *user_data)
{
struct netdev *netdev = user_data;
struct handshake_state *hs = netdev->handshake;
struct l_genl_msg *msg;
struct iovec iov[64];
unsigned int n_iov = L_ARRAY_SIZE(iov);
unsigned int c_iov = 0;
enum mpdu_management_subtype subtype =
MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST;
msg = netdev_build_cmd_associate_common(netdev);
c_iov = netdev_populate_common_ies(netdev, hs, msg, iov, n_iov, c_iov);
if (!L_WARN_ON(n_iov - c_iov < n_owe_iov)) {
memcpy(iov + c_iov, owe_iov, sizeof(*owe_iov) * n_owe_iov);
c_iov += n_owe_iov;
}
mpdu_sort_ies(subtype, iov, c_iov);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, c_iov);
/* If doing a non-FT Reassociation */
if (netdev->in_reassoc)
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, 6,
netdev->ap->prev_bssid);
if (!l_genl_family_send(nl80211, msg, netdev_assoc_cb,
netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_fils_tx_authenticate(const uint8_t *body,
size_t body_len,
void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_authenticate(netdev, NL80211_AUTHTYPE_FILS_SK);
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_DATA, body_len, body);
if (!l_genl_family_send(nl80211, msg, netdev_auth_cb,
netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
netdev->auth_cmd = l_genl_msg_ref(msg);
}
static void netdev_fils_tx_associate(struct iovec *fils_iov, size_t n_fils_iov,
const uint8_t *kek, size_t kek_len,
const uint8_t *nonces,
size_t nonces_len,
void *user_data)
{
struct netdev *netdev = user_data;
struct handshake_state *hs = netdev->handshake;
struct l_genl_msg *msg;
struct iovec iov[64];
unsigned int n_iov = L_ARRAY_SIZE(iov);
unsigned int c_iov = 0;
enum mpdu_management_subtype subtype =
MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST;
msg = netdev_build_cmd_associate_common(netdev);
c_iov = netdev_populate_common_ies(netdev, hs, msg, iov, n_iov, c_iov);
if (!L_WARN_ON(n_iov - c_iov < n_fils_iov)) {
memcpy(iov + c_iov, fils_iov, sizeof(*fils_iov) * n_fils_iov);
c_iov += n_fils_iov;
}
mpdu_sort_ies(subtype, iov, c_iov);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, c_iov);
l_genl_msg_append_attr(msg, NL80211_ATTR_FILS_KEK, kek_len, kek);
l_genl_msg_append_attr(msg, NL80211_ATTR_FILS_NONCES,
nonces_len, nonces);
/* If doing a non-FT Reassociation */
if (netdev->in_reassoc)
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, 6,
netdev->ap->prev_bssid);
if (!l_genl_family_send(nl80211, msg, netdev_assoc_cb,
netdev, NULL)) {
l_genl_msg_unref(msg);
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
2016-06-27 23:35:31 +02:00
static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev,
struct scan_bss *bss,
struct handshake_state *hs,
const uint8_t *prev_bssid,
const struct iovec *vendor_ies,
size_t num_vendor_ies)
{
struct netdev_handshake_state *nhs =
l_container_of(hs, struct netdev_handshake_state, super);
uint32_t auth_type = IE_AKM_IS_SAE(hs->akm_suite) ?
NL80211_AUTHTYPE_SAE :
NL80211_AUTHTYPE_OPEN_SYSTEM;
enum mpdu_management_subtype subtype = prev_bssid ?
MPDU_MANAGEMENT_SUBTYPE_REASSOCIATION_REQUEST :
MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_REQUEST;
struct l_genl_msg *msg;
struct iovec iov[64];
unsigned int n_iov = L_ARRAY_SIZE(iov);
unsigned int c_iov = 0;
bool is_rsn = hs->supplicant_ie != NULL;
2016-06-27 23:35:31 +02:00
msg = l_genl_msg_new_sized(NL80211_CMD_CONNECT, 512);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ,
4, &bss->frequency);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, bss->addr);
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID,
bss->ssid_len, bss->ssid);
2016-06-27 23:35:31 +02:00
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type);
switch (nhs->type) {
case CONNECTION_TYPE_SOFTMAC:
case CONNECTION_TYPE_FULLMAC:
break;
case CONNECTION_TYPE_SAE_OFFLOAD:
l_genl_msg_append_attr(msg, NL80211_ATTR_SAE_PASSWORD,
strlen(hs->passphrase), hs->passphrase);
break;
case CONNECTION_TYPE_PSK_OFFLOAD:
l_genl_msg_append_attr(msg, NL80211_ATTR_PMK, 32, hs->pmk);
break;
case CONNECTION_TYPE_8021X_OFFLOAD:
l_genl_msg_append_attr(msg, NL80211_ATTR_WANT_1X_4WAY_HS,
0, NULL);
}
if (prev_bssid)
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN,
prev_bssid);
2016-06-27 23:35:31 +02:00
if (bss->capability & IE_BSS_CAP_PRIVACY)
l_genl_msg_append_attr(msg, NL80211_ATTR_PRIVACY, 0, NULL);
l_genl_msg_append_attr(msg, NL80211_ATTR_SOCKET_OWNER, 0, NULL);
if (is_rsn) {
uint32_t nl_cipher;
uint32_t nl_akm;
uint32_t wpa_version;
if (hs->pairwise_cipher == IE_RSN_CIPHER_SUITE_CCMP)
nl_cipher = CRYPTO_CIPHER_CCMP;
else
nl_cipher = CRYPTO_CIPHER_TKIP;
l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE,
4, &nl_cipher);
if (hs->group_cipher == IE_RSN_CIPHER_SUITE_CCMP)
nl_cipher = CRYPTO_CIPHER_CCMP;
else
nl_cipher = CRYPTO_CIPHER_TKIP;
l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITE_GROUP,
4, &nl_cipher);
if (hs->mfp) {
uint32_t use_mfp = NL80211_MFP_REQUIRED;
l_genl_msg_append_attr(msg, NL80211_ATTR_USE_MFP,
4, &use_mfp);
}
nl_akm = ie_rsn_akm_suite_to_nl80211(hs->akm_suite);
if (nl_akm)
l_genl_msg_append_attr(msg, NL80211_ATTR_AKM_SUITES,
4, &nl_akm);
if (IE_AKM_IS_SAE(hs->akm_suite))
wpa_version = NL80211_WPA_VERSION_3;
else if (hs->wpa_ie)
wpa_version = NL80211_WPA_VERSION_1;
else
wpa_version = NL80211_WPA_VERSION_2;
l_genl_msg_append_attr(msg, NL80211_ATTR_WPA_VERSIONS,
4, &wpa_version);
l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT, 0, NULL);
c_iov = iov_ie_append(iov, n_iov, c_iov, hs->supplicant_ie);
}
if (netdev->pae_over_nl80211)
l_genl_msg_append_attr(msg,
NL80211_ATTR_CONTROL_PORT_OVER_NL80211,
0, NULL);
c_iov = iov_ie_append(iov, n_iov, c_iov, hs->mde);
c_iov = netdev_populate_common_ies(netdev, hs, msg, iov, n_iov, c_iov);
mpdu_sort_ies(subtype, iov, c_iov);
if (vendor_ies && !L_WARN_ON(n_iov - c_iov < num_vendor_ies)) {
memcpy(iov + c_iov, vendor_ies,
sizeof(*vendor_ies) * num_vendor_ies);
c_iov += num_vendor_ies;
}
if (c_iov)
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, c_iov);
return msg;
}
struct rtnl_data {
struct netdev *netdev;
uint8_t addr[ETH_ALEN];
int ref;
};
static int netdev_begin_connection(struct netdev *netdev)
{
if (netdev->connect_cmd) {
netdev->connect_cmd_id = l_genl_family_send(nl80211,
netdev->connect_cmd,
netdev_cmd_connect_cb, netdev,
NULL);
netdev: Cancel the CMD_CONNECT genl command on disconnect CMD_DISCONNECT fails on some occasions when CMD_CONNECT is still running. When this happens the DBus disconnect command receives an error reply but iwd's device state is left as disconnected even though there's a connection at the kernel level which times out a few seconds later. If the CMD_CONNECT is cancelled I couldn't reproduce this so far. src/network.c:network_connect() src/network.c:network_connect_psk() src/network.c:network_connect_psk() psk: 69ae3f8b2f84a438cf6a44275913182dd2714510ccb8cbdf8da9dc8b61718560 src/network.c:network_connect_psk() len: 32 src/network.c:network_connect_psk() ask_psk: false src/device.c:device_enter_state() Old State: disconnected, new state: connecting src/scan.c:scan_notify() Scan notification 33 src/device.c:device_netdev_event() Associating src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/device.c:device_dbus_disconnect() src/device.c:device_connect_cb() 6, result: 5 src/device.c:device_enter_state() Old State: connecting, new state: disconnecting src/device.c:device_disconnect_cb() 6, success: 0 src/device.c:device_enter_state() Old State: disconnecting, new state: disconnected src/scan.c:scan_notify() Scan notification 34 src/netdev.c:netdev_mlme_notify() MLME notification 19 src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 37 src/netdev.c:netdev_authenticate_event() src/scan.c:get_scan_callback() get_scan_callback src/scan.c:get_scan_done() get_scan_done src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 19 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 38 src/netdev.c:netdev_associate_event() src/netdev.c:netdev_mlme_notify() MLME notification 46 src/netdev.c:netdev_connect_event() <delay> src/netdev.c:netdev_mlme_notify() MLME notification 60 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 MLME notification is missing ifindex attribute src/netdev.c:netdev_mlme_notify() MLME notification 20 src/netdev.c:netdev_mlme_notify() MLME notification 39 src/netdev.c:netdev_deauthenticate_event()
2016-08-05 14:25:34 +02:00
if (!netdev->connect_cmd_id)
goto failed;
netdev->connect_cmd = NULL;
}
/*
* Set the supplicant address now, this may have already been done for
* a non-randomized address connect, but if we are randomizing we need
* to set it again as the address should have now changed.
*/
handshake_state_set_supplicant_address(netdev->handshake, netdev->addr);
if (netdev->ap) {
if (!auth_proto_start(netdev->ap))
goto failed;
/*
* set connected since the auth protocols cannot do
* so internally
*/
netdev->connected = true;
}
return 0;
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return -EIO;
}
static void netdev_mac_change_failed(struct netdev *netdev,
struct rtnl_data *req, int error)
{
l_error("Error setting mac address on %d: %s", netdev->index,
strerror(-error));
/*
* If the interface is down and we failed to up it we need to notify
* any watchers since we have been skipping the notification while
* mac_change_cmd_id was set.
*/
if (!netdev_get_is_up(netdev)) {
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DOWN);
goto failed;
} else {
/*
* If the interface is up we can still try and connect. This
* is a very rare case and most likely will never happen.
*/
l_info("Interface still up after failing to change the MAC, "
"continuing with connection");
if (netdev_begin_connection(netdev) < 0)
goto failed;
return;
}
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static void netdev_mac_destroy(void *user_data)
{
struct rtnl_data *req = user_data;
req->ref--;
/* still pending requests? */
if (req->ref)
return;
l_free(req);
}
static void netdev_mac_power_up_cb(int error, uint16_t type,
const void *data, uint32_t len,
void *user_data)
{
struct rtnl_data *req = user_data;
struct netdev *netdev = req->netdev;
netdev->mac_change_cmd_id = 0;
if (error) {
l_error("Error taking interface %u up for per-network MAC "
"generation: %s", netdev->index, strerror(-error));
netdev_mac_change_failed(netdev, req, error);
return;
}
/*
* Pick up where we left off in netdev_connect_commmon.
*/
if (netdev_begin_connection(netdev) < 0) {
l_error("Failed to connect after changing MAC");
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_mac_power_down_cb(int error, uint16_t type,
const void *data, uint32_t len,
void *user_data)
{
struct rtnl_data *req = user_data;
struct netdev *netdev = req->netdev;
netdev->mac_change_cmd_id = 0;
if (error) {
l_error("Error taking interface %u down for per-network MAC "
"generation: %s", netdev->index, strerror(-error));
netdev_mac_change_failed(netdev, req, error);
return;
}
l_debug("Setting generated address on ifindex: %d to: "MAC,
netdev->index, MAC_STR(req->addr));
netdev->mac_change_cmd_id = l_rtnl_set_mac(rtnl, netdev->index,
req->addr, true,
netdev_mac_power_up_cb, req,
netdev_mac_destroy);
if (!netdev->mac_change_cmd_id) {
netdev_mac_change_failed(netdev, req, -EIO);
return;
}
req->ref++;
}
/*
* TODO: There are some potential race conditions that are being ignored. There
* is nothing that IWD itself can do to solve these, they require kernel
* changes:
*
* 1. A perfectly timed ifdown could be ignored. If an external process
* brings down an interface just before calling this function we would only
* get a single newlink event since there is no state change doing a second
* ifdown (nor an error from the kernel). This newlink event would be ignored
* since IWD thinks its from our own doing. This would result in IWD changing
* the MAC and bringing the interface back up which would look very strange
* and unexpected to someone who just tried to ifdown an interface.
*
* 2. A perfectly timed ifup could result in a failed connection. If an external
* process ifup's just after IWD ifdown's but before changing the MAC this
* would cause the MAC change to fail. This failure would result in a failed
* connection.
*
* Returns 0 if a MAC change procedure was started.
* Returns -EALREADY if the requested MAC matched our current MAC
* Returns -EIO if there was an IO error when powering down
*/
static int netdev_start_powered_mac_change(struct netdev *netdev)
{
struct rtnl_data *req;
uint8_t new_addr[6];
/* No address set in handshake, use per-network MAC generation */
if (l_memeqzero(netdev->handshake->spa, ETH_ALEN))
wiphy_generate_address_from_ssid(netdev->wiphy,
(const char *)netdev->handshake->ssid,
new_addr);
else
memcpy(new_addr, netdev->handshake->spa, ETH_ALEN);
/*
* MAC has already been changed previously, no need to again
*/
if (!memcmp(new_addr, netdev->addr, sizeof(new_addr)))
return -EALREADY;
req = l_new(struct rtnl_data, 1);
req->netdev = netdev;
/* This message will need to be unreffed upon any error */
req->ref++;
memcpy(req->addr, new_addr, sizeof(req->addr));
netdev->mac_change_cmd_id = l_rtnl_set_powered(rtnl, netdev->index,
false, netdev_mac_power_down_cb,
req, netdev_mac_destroy);
if (!netdev->mac_change_cmd_id) {
l_free(req);
return -EIO;
}
return 0;
}
static struct l_genl_msg *netdev_build_cmd_cqm_rssi_update(
struct netdev *netdev,
const int8_t *levels,
size_t levels_num)
{
struct l_genl_msg *msg;
uint32_t hyst = 5;
int thold_count;
int32_t thold_list[levels_num + 2];
int threshold = netdev->frequency > 4000 ? LOW_SIGNAL_THRESHOLD_5GHZ :
LOW_SIGNAL_THRESHOLD;
if (levels_num == 0) {
thold_list[0] = threshold;
thold_count = 1;
} else {
/*
* Build the list of all the threshold values we care about:
* - the low/high level threshold,
* - the value ranges requested by
* netdev_set_rssi_report_levels
*/
unsigned int i;
bool low_sig_added = false;
thold_count = 0;
for (i = 0; i < levels_num; i++) {
int32_t val = levels[levels_num - i - 1];
if (i && thold_list[thold_count - 1] >= val)
return NULL;
if (val >= threshold && !low_sig_added) {
thold_list[thold_count++] = threshold;
low_sig_added = true;
/* Duplicate values are not allowed */
if (val == threshold)
continue;
}
thold_list[thold_count++] = val;
}
}
msg = l_genl_msg_new_sized(NL80211_CMD_SET_CQM, 32 + thold_count * 4);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_enter_nested(msg, NL80211_ATTR_CQM);
l_genl_msg_append_attr(msg, NL80211_ATTR_CQM_RSSI_THOLD,
thold_count * 4, thold_list);
l_genl_msg_append_attr(msg, NL80211_ATTR_CQM_RSSI_HYST, 4, &hyst);
l_genl_msg_leave_nested(msg);
return msg;
}
static void netdev_cmd_set_cqm_cb(struct l_genl_msg *msg, void *user_data)
{
int err = l_genl_msg_get_error(msg);
const char *ext_error;
if (err >= 0)
return;
ext_error = l_genl_msg_get_extended_error(msg);
l_error("CMD_SET_CQM failed: %s",
ext_error ? ext_error : strerror(-err));
}
static int netdev_cqm_rssi_update(struct netdev *netdev)
{
struct l_genl_msg *msg;
l_debug("");
if (!wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CQM_RSSI_LIST))
msg = netdev_build_cmd_cqm_rssi_update(netdev, NULL, 0);
else
msg = netdev_build_cmd_cqm_rssi_update(netdev,
netdev->rssi_levels,
netdev->rssi_levels_num);
if (!msg)
return -EINVAL;
if (!l_genl_family_send(nl80211, msg, netdev_cmd_set_cqm_cb,
NULL, NULL)) {
l_genl_msg_unref(msg);
return -EIO;
}
return 0;
}
static bool netdev_connection_work_ready(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
netdev->retry_auth = false;
if (mac_per_ssid) {
int ret = netdev_start_powered_mac_change(netdev);
if (!ret)
return false;
else if (ret != -EALREADY)
goto failed;
}
if (netdev_begin_connection(netdev) < 0)
goto failed;
return false;
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return true;
}
static void netdev_connection_work_destroy(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
if (netdev->auth_cmd) {
l_genl_msg_unref(netdev->auth_cmd);
netdev->auth_cmd = NULL;
}
netdev->retry_auth = false;
}
static const struct wiphy_radio_work_item_ops connect_work_ops = {
.do_work = netdev_connection_work_ready,
.destroy = netdev_connection_work_destroy,
};
static int netdev_handshake_state_setup_connection_type(
struct handshake_state *hs)
{
struct netdev_handshake_state *nhs = l_container_of(hs,
struct netdev_handshake_state, super);
struct wiphy *wiphy = nhs->netdev->wiphy;
bool softmac = wiphy_supports_cmds_auth_assoc(wiphy);
bool canroam = wiphy_supports_firmware_roam(wiphy);
if (hs->supplicant_ie == NULL)
goto softmac;
/*
* Sanity check that any FT AKMs are set only on softmac or on
* devices that support firmware roam
*/
if (L_WARN_ON(IE_AKM_IS_FT(hs->akm_suite) && !softmac && !canroam))
return -ENOTSUP;
switch (hs->akm_suite) {
case IE_RSN_AKM_SUITE_PSK:
case IE_RSN_AKM_SUITE_FT_USING_PSK:
case IE_RSN_AKM_SUITE_PSK_SHA256:
if (wiphy_has_ext_feature(wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK))
goto psk_offload;
if (softmac)
goto softmac;
goto fullmac;
case IE_RSN_AKM_SUITE_8021X:
case IE_RSN_AKM_SUITE_FT_OVER_8021X:
case IE_RSN_AKM_SUITE_8021X_SHA256:
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256:
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384:
case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384:
if (wiphy_has_ext_feature(wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X))
goto offload_1x;
if (softmac)
goto softmac;
goto fullmac;
case IE_RSN_AKM_SUITE_SAE_SHA256:
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
if (wiphy_has_ext_feature(wiphy,
NL80211_EXT_FEATURE_SAE_OFFLOAD))
goto sae_offload;
if (softmac && wiphy_has_feature(wiphy, NL80211_FEATURE_SAE))
goto softmac;
return -EINVAL;
case IE_RSN_AKM_SUITE_OWE:
case IE_RSN_AKM_SUITE_FILS_SHA256:
case IE_RSN_AKM_SUITE_FILS_SHA384:
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
/* FILS and OWE have no offload in any upstream driver */
if (softmac)
goto softmac;
return -ENOTSUP;
case IE_RSN_AKM_SUITE_TDLS:
case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256:
case IE_RSN_AKM_SUITE_OSEN:
return -ENOTSUP;
}
return -ENOTSUP;
softmac:
nhs->type = CONNECTION_TYPE_SOFTMAC;
return 0;
fullmac:
nhs->type = CONNECTION_TYPE_FULLMAC;
return 0;
sae_offload:
nhs->type = CONNECTION_TYPE_SAE_OFFLOAD;
return 0;
psk_offload:
nhs->type = CONNECTION_TYPE_PSK_OFFLOAD;
return 0;
offload_1x:
nhs->type = CONNECTION_TYPE_8021X_OFFLOAD;
return 0;
}
static void netdev_connect_common(struct netdev *netdev,
struct scan_bss *bss,
struct scan_bss *prev_bss,
struct handshake_state *hs,
const struct iovec *vendor_ies,
size_t num_vendor_ies,
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
2016-09-14 03:50:24 +02:00
{
struct netdev_handshake_state *nhs = l_container_of(hs,
struct netdev_handshake_state, super);
struct l_genl_msg *cmd_connect = NULL;
struct eapol_sm *sm = NULL;
bool is_rsn = hs->supplicant_ie != NULL;
const uint8_t *prev_bssid = prev_bss ? prev_bss->addr : NULL;
2016-09-14 03:50:24 +02:00
if (!is_rsn)
goto build_cmd_connect;
if (nhs->type != CONNECTION_TYPE_SOFTMAC)
goto build_cmd_connect;
switch (hs->akm_suite) {
case IE_RSN_AKM_SUITE_SAE_SHA256:
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
netdev->ap = sae_sm_new(hs, netdev_sae_tx_authenticate,
netdev_sae_tx_associate,
netdev);
if (sae_sm_is_h2e(netdev->ap)) {
uint8_t own_rsnxe[20];
if (wiphy_get_rsnxe(netdev->wiphy,
own_rsnxe, sizeof(own_rsnxe))) {
set_bit(own_rsnxe + 2, IE_RSNX_SAE_H2E);
handshake_state_set_supplicant_rsnxe(hs,
own_rsnxe);
}
}
break;
case IE_RSN_AKM_SUITE_OWE:
netdev->ap = owe_sm_new(hs, netdev_owe_tx_authenticate,
netdev_owe_tx_associate,
netdev);
break;
case IE_RSN_AKM_SUITE_FILS_SHA256:
case IE_RSN_AKM_SUITE_FILS_SHA384:
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
netdev->ap = fils_sm_new(hs, netdev_fils_tx_authenticate,
netdev_fils_tx_associate,
netdev);
break;
default:
build_cmd_connect:
cmd_connect = netdev_build_cmd_connect(netdev, bss, hs,
prev_bssid, vendor_ies, num_vendor_ies);
2016-09-14 03:50:24 +02:00
if (!is_offload(hs) && (is_rsn || hs->settings_8021x)) {
sm = eapol_sm_new(hs);
if (nhs->type == CONNECTION_TYPE_8021X_OFFLOAD)
eapol_sm_set_require_handshake(sm, false);
}
}
netdev->connect_cmd = cmd_connect;
netdev->event_filter = event_filter;
netdev->connect_cb = cb;
netdev->user_data = user_data;
netdev->handshake = hs;
netdev->sm = sm;
netdev->frequency = bss->frequency;
netdev->cur_rssi = bss->signal_strength / 100;
netdev_rssi_level_init(netdev);
netdev_cqm_rssi_update(netdev);
handshake_state_set_authenticator_address(hs, bss->addr);
if (!wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
handshake_state_set_no_rekey(hs, true);
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&connect_work_ops);
2016-09-14 03:50:24 +02:00
}
int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
struct handshake_state *hs,
const struct iovec *vendor_ies,
size_t num_vendor_ies,
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
if (!(netdev->ifi_flags & IFF_UP))
return -ENETDOWN;
if (netdev->type != NL80211_IFTYPE_STATION &&
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
if (netdev->connected || netdev->connect_cmd_id || netdev->work.id)
return -EISCONN;
if (netdev_handshake_state_setup_connection_type(hs) < 0)
return -ENOTSUP;
netdev_connect_common(netdev, bss, NULL, hs, vendor_ies,
num_vendor_ies, event_filter, cb,
user_data);
return 0;
}
static void disconnect_idle(struct l_idle *idle, void *user_data)
netdev: fix crash from carefully timed Connect() This crash was caused from the disconnect_cb being called immediately in cases where send_disconnect was false. The previous patch actually addressed this separately as this flag was being set improperly which will, indirectly, fix one of the two code paths that could cause this crash. Still, there is a situation where send_disconnect could be false and in this case IWD would still crash. If IWD is waiting to queue the connect item and netdev_disconnect is called it would result in the callback being called immediately. Instead we can add an l_idle as to allow the callback to happen out of scope, which is what station expects. Prior to this patch, the crashing behavior can be tested using the following script (or some variant of it, your system timing may not be the same as mine). iwctl station wlan0 disconnect iwctl station wlan0 connect <network1> & sleep 0.02 iwctl station wlan0 connect <network2> ++++++++ backtrace ++++++++ 0 0x7f4e1504e530 in /lib64/libc.so.6 1 0x432b54 in network_get_security() at src/network.c:253 2 0x416e92 in station_handshake_setup() at src/station.c:937 3 0x41a505 in __station_connect_network() at src/station.c:2551 4 0x41a683 in station_disconnect_onconnect_cb() at src/station.c:2581 5 0x40b4ae in netdev_disconnect() at src/netdev.c:3142 6 0x41a719 in station_disconnect_onconnect() at src/station.c:2603 7 0x41a89d in station_connect_network() at src/station.c:2652 8 0x433f1d in network_connect_psk() at src/network.c:886 9 0x43483a in network_connect() at src/network.c:1183 10 0x4add11 in _dbus_object_tree_dispatch() at ell/dbus-service.c:1802 11 0x49ff54 in message_read_handler() at ell/dbus.c:285 12 0x496d2f in io_callback() at ell/io.c:120 13 0x495894 in l_main_iterate() at ell/main.c:478 14 0x49599b in l_main_run() at ell/main.c:521 15 0x495cb3 in l_main_run_with_signal() at ell/main.c:647 16 0x404add in main() at src/main.c:490 17 0x7f4e15038b25 in /lib64/libc.so.6
2021-04-06 00:18:36 +02:00
{
struct netdev *netdev = user_data;
l_idle_remove(idle);
netdev->disconnect_idle = NULL;
netdev: fix crash from carefully timed Connect() This crash was caused from the disconnect_cb being called immediately in cases where send_disconnect was false. The previous patch actually addressed this separately as this flag was being set improperly which will, indirectly, fix one of the two code paths that could cause this crash. Still, there is a situation where send_disconnect could be false and in this case IWD would still crash. If IWD is waiting to queue the connect item and netdev_disconnect is called it would result in the callback being called immediately. Instead we can add an l_idle as to allow the callback to happen out of scope, which is what station expects. Prior to this patch, the crashing behavior can be tested using the following script (or some variant of it, your system timing may not be the same as mine). iwctl station wlan0 disconnect iwctl station wlan0 connect <network1> & sleep 0.02 iwctl station wlan0 connect <network2> ++++++++ backtrace ++++++++ 0 0x7f4e1504e530 in /lib64/libc.so.6 1 0x432b54 in network_get_security() at src/network.c:253 2 0x416e92 in station_handshake_setup() at src/station.c:937 3 0x41a505 in __station_connect_network() at src/station.c:2551 4 0x41a683 in station_disconnect_onconnect_cb() at src/station.c:2581 5 0x40b4ae in netdev_disconnect() at src/netdev.c:3142 6 0x41a719 in station_disconnect_onconnect() at src/station.c:2603 7 0x41a89d in station_connect_network() at src/station.c:2652 8 0x433f1d in network_connect_psk() at src/network.c:886 9 0x43483a in network_connect() at src/network.c:1183 10 0x4add11 in _dbus_object_tree_dispatch() at ell/dbus-service.c:1802 11 0x49ff54 in message_read_handler() at ell/dbus.c:285 12 0x496d2f in io_callback() at ell/io.c:120 13 0x495894 in l_main_iterate() at ell/main.c:478 14 0x49599b in l_main_run() at ell/main.c:521 15 0x495cb3 in l_main_run_with_signal() at ell/main.c:647 16 0x404add in main() at src/main.c:490 17 0x7f4e15038b25 in /lib64/libc.so.6
2021-04-06 00:18:36 +02:00
netdev->disconnect_cb(netdev, true, netdev->user_data);
}
int netdev_disconnect(struct netdev *netdev,
netdev_disconnect_cb_t cb, void *user_data)
{
struct l_genl_msg *disconnect;
bool send_disconnect = true;
if (!(netdev->ifi_flags & IFF_UP))
return -ENETDOWN;
if (netdev->type != NL80211_IFTYPE_STATION &&
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
if (netdev->disconnect_cmd_id)
return -EINPROGRESS;
/* Only perform this if we haven't successfully fully associated yet */
if (!netdev->operational) {
/*
* Three possibilities here:
* 1. We do not actually have a connect in progress (work.id
* is zero), then we can bail out early with an error.
* 2. We have sent CMD_CONNECT but not fully connected. The
* CMD_CONNECT needs to be canceled and a disconnect should
* be sent.
* 3. Queued up the connect work, but haven't sent CMD_CONNECT
* to the kernel. This case we do not need to send a
* disconnect.
*/
if (!netdev->work.id)
return -ENOTCONN;
if (netdev->connect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
netdev->connect_cmd_id = 0;
} else if (!wiphy_radio_work_is_running(netdev->wiphy,
netdev->work.id))
send_disconnect = false;
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
} else {
netdev_connect_free(netdev);
}
if (send_disconnect) {
disconnect = netdev_build_cmd_disconnect(netdev,
MMPDU_REASON_CODE_DEAUTH_LEAVING);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211,
disconnect, netdev_cmd_disconnect_cb,
netdev, NULL);
if (!netdev->disconnect_cmd_id) {
l_genl_msg_unref(disconnect);
return -EIO;
}
netdev->disconnect_cb = cb;
netdev->user_data = user_data;
netdev->aborting = true;
netdev: fix crash from carefully timed Connect() This crash was caused from the disconnect_cb being called immediately in cases where send_disconnect was false. The previous patch actually addressed this separately as this flag was being set improperly which will, indirectly, fix one of the two code paths that could cause this crash. Still, there is a situation where send_disconnect could be false and in this case IWD would still crash. If IWD is waiting to queue the connect item and netdev_disconnect is called it would result in the callback being called immediately. Instead we can add an l_idle as to allow the callback to happen out of scope, which is what station expects. Prior to this patch, the crashing behavior can be tested using the following script (or some variant of it, your system timing may not be the same as mine). iwctl station wlan0 disconnect iwctl station wlan0 connect <network1> & sleep 0.02 iwctl station wlan0 connect <network2> ++++++++ backtrace ++++++++ 0 0x7f4e1504e530 in /lib64/libc.so.6 1 0x432b54 in network_get_security() at src/network.c:253 2 0x416e92 in station_handshake_setup() at src/station.c:937 3 0x41a505 in __station_connect_network() at src/station.c:2551 4 0x41a683 in station_disconnect_onconnect_cb() at src/station.c:2581 5 0x40b4ae in netdev_disconnect() at src/netdev.c:3142 6 0x41a719 in station_disconnect_onconnect() at src/station.c:2603 7 0x41a89d in station_connect_network() at src/station.c:2652 8 0x433f1d in network_connect_psk() at src/network.c:886 9 0x43483a in network_connect() at src/network.c:1183 10 0x4add11 in _dbus_object_tree_dispatch() at ell/dbus-service.c:1802 11 0x49ff54 in message_read_handler() at ell/dbus.c:285 12 0x496d2f in io_callback() at ell/io.c:120 13 0x495894 in l_main_iterate() at ell/main.c:478 14 0x49599b in l_main_run() at ell/main.c:521 15 0x495cb3 in l_main_run_with_signal() at ell/main.c:647 16 0x404add in main() at src/main.c:490 17 0x7f4e15038b25 in /lib64/libc.so.6
2021-04-06 00:18:36 +02:00
} else if (cb) {
netdev->disconnect_cb = cb;
netdev->user_data = user_data;
netdev->disconnect_idle = l_idle_create(disconnect_idle,
netdev, NULL);
netdev: fix crash from carefully timed Connect() This crash was caused from the disconnect_cb being called immediately in cases where send_disconnect was false. The previous patch actually addressed this separately as this flag was being set improperly which will, indirectly, fix one of the two code paths that could cause this crash. Still, there is a situation where send_disconnect could be false and in this case IWD would still crash. If IWD is waiting to queue the connect item and netdev_disconnect is called it would result in the callback being called immediately. Instead we can add an l_idle as to allow the callback to happen out of scope, which is what station expects. Prior to this patch, the crashing behavior can be tested using the following script (or some variant of it, your system timing may not be the same as mine). iwctl station wlan0 disconnect iwctl station wlan0 connect <network1> & sleep 0.02 iwctl station wlan0 connect <network2> ++++++++ backtrace ++++++++ 0 0x7f4e1504e530 in /lib64/libc.so.6 1 0x432b54 in network_get_security() at src/network.c:253 2 0x416e92 in station_handshake_setup() at src/station.c:937 3 0x41a505 in __station_connect_network() at src/station.c:2551 4 0x41a683 in station_disconnect_onconnect_cb() at src/station.c:2581 5 0x40b4ae in netdev_disconnect() at src/netdev.c:3142 6 0x41a719 in station_disconnect_onconnect() at src/station.c:2603 7 0x41a89d in station_connect_network() at src/station.c:2652 8 0x433f1d in network_connect_psk() at src/network.c:886 9 0x43483a in network_connect() at src/network.c:1183 10 0x4add11 in _dbus_object_tree_dispatch() at ell/dbus-service.c:1802 11 0x49ff54 in message_read_handler() at ell/dbus.c:285 12 0x496d2f in io_callback() at ell/io.c:120 13 0x495894 in l_main_iterate() at ell/main.c:478 14 0x49599b in l_main_run() at ell/main.c:521 15 0x495cb3 in l_main_run_with_signal() at ell/main.c:647 16 0x404add in main() at src/main.c:490 17 0x7f4e15038b25 in /lib64/libc.so.6
2021-04-06 00:18:36 +02:00
}
return 0;
}
int netdev_reassociate(struct netdev *netdev, struct scan_bss *target_bss,
struct scan_bss *orig_bss, struct handshake_state *hs,
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
struct handshake_state *old_hs;
struct eapol_sm *old_sm;
old_sm = netdev->sm;
old_hs = netdev->handshake;
if (netdev_handshake_state_setup_connection_type(hs) < 0)
return -ENOTSUP;
netdev->associated = false;
netdev->operational = false;
netdev->connected = false;
netdev->in_reassoc = true;
netdev_connect_common(netdev, target_bss, orig_bss, hs, NULL, 0,
event_filter, cb, user_data);
if (netdev->ap)
memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN);
netdev_rssi_polling_update(netdev);
if (old_sm)
eapol_sm_free(old_sm);
if (old_hs)
handshake_state_free(old_hs);
return 0;
}
static void netdev_join_adhoc_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
netdev->join_adhoc_cmd_id = 0;
if (netdev->adhoc_cb)
netdev->adhoc_cb(netdev, l_genl_msg_get_error(msg),
netdev->user_data);
}
int netdev_join_adhoc(struct netdev *netdev, const char *ssid,
struct iovec *extra_ie, size_t extra_ie_elems,
bool control_port, netdev_command_cb_t cb,
void *user_data)
{
struct l_genl_msg *cmd;
uint32_t ifindex = netdev->index;
uint32_t ch_freq = scan_channel_to_freq(6, SCAN_BAND_2_4_GHZ);
uint32_t ch_type = NL80211_CHAN_HT20;
if (netdev->type != NL80211_IFTYPE_ADHOC) {
l_error("iftype is invalid for adhoc: %u",
netdev_get_iftype(netdev));
return -ENOTSUP;
}
if (netdev->join_adhoc_cmd_id || netdev->leave_adhoc_cmd_id)
return -EBUSY;
netdev->adhoc_cb = cb;
netdev->user_data = user_data;
cmd = l_genl_msg_new_sized(NL80211_CMD_JOIN_IBSS, 128);
l_genl_msg_append_attr(cmd, NL80211_ATTR_IFINDEX, 4, &ifindex);
l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, strlen(ssid), ssid);
l_genl_msg_append_attr(cmd, NL80211_ATTR_WIPHY_FREQ, 4, &ch_freq);
l_genl_msg_append_attr(cmd, NL80211_ATTR_WIPHY_CHANNEL_TYPE, 4,
&ch_type);
l_genl_msg_append_attrv(cmd, NL80211_ATTR_IE, extra_ie, extra_ie_elems);
l_genl_msg_append_attr(cmd, NL80211_ATTR_SOCKET_OWNER, 0, NULL);
if (control_port) {
l_genl_msg_append_attr(cmd, NL80211_ATTR_CONTROL_PORT, 0, NULL);
if (netdev->pae_over_nl80211)
l_genl_msg_append_attr(cmd,
NL80211_ATTR_CONTROL_PORT_OVER_NL80211,
0, NULL);
}
netdev->join_adhoc_cmd_id = l_genl_family_send(nl80211, cmd,
netdev_join_adhoc_cb, netdev, NULL);
if (!netdev->join_adhoc_cmd_id) {
netdev->adhoc_cb = NULL;
netdev->user_data = NULL;
return -EIO;
}
return 0;
}
static void netdev_leave_adhoc_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
netdev->leave_adhoc_cmd_id = 0;
if (netdev->adhoc_cb)
netdev->adhoc_cb(netdev, l_genl_msg_get_error(msg),
netdev->user_data);
netdev->adhoc_cb = NULL;
}
int netdev_leave_adhoc(struct netdev *netdev, netdev_command_cb_t cb,
void *user_data)
{
struct l_genl_msg *cmd;
if (netdev->type != NL80211_IFTYPE_ADHOC) {
l_error("iftype is invalid for adhoc: %u",
netdev_get_iftype(netdev));
return -ENOTSUP;
}
if (netdev->join_adhoc_cmd_id || netdev->leave_adhoc_cmd_id)
return -EBUSY;
netdev->adhoc_cb = cb;
netdev->user_data = user_data;
cmd = l_genl_msg_new_sized(NL80211_CMD_LEAVE_IBSS, 64);
l_genl_msg_append_attr(cmd, NL80211_ATTR_IFINDEX, 4, &netdev->index);
netdev->leave_adhoc_cmd_id = l_genl_family_send(nl80211, cmd,
netdev_leave_adhoc_cb, netdev,
NULL);
if (!netdev->leave_adhoc_cmd_id)
return -EIO;
return 0;
}
static uint32_t netdev_send_action_framev(struct netdev *netdev,
const uint8_t *to,
struct iovec *iov, size_t iov_len,
uint32_t freq,
l_genl_msg_func_t callback,
void *user_data)
{
uint32_t id;
struct l_genl_msg *msg = nl80211_build_cmd_frame(netdev->index,
netdev->addr,
to, freq,
iov, iov_len);
id = l_genl_family_send(nl80211, msg, callback, user_data, NULL);
if (!id)
l_genl_msg_unref(msg);
return id;
}
static uint32_t netdev_send_action_frame(struct netdev *netdev,
const uint8_t *to,
const uint8_t *body, size_t body_len,
uint32_t freq,
l_genl_msg_func_t callback,
void *user_data)
{
struct iovec iov[1];
iov[0].iov_base = (void *)body;
iov[0].iov_len = body_len;
return netdev_send_action_framev(netdev, to, iov, 1, freq, callback,
user_data);
}
static void netdev_cmd_authenticate_ft_cb(struct l_genl_msg *msg,
void *user_data)
{
struct netdev *netdev = user_data;
netdev->connect_cmd_id = 0;
if (l_genl_msg_get_error(msg) < 0)
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static void netdev_ft_tx_authenticate(struct iovec *iov,
size_t iov_len, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *cmd_authenticate;
cmd_authenticate = netdev_build_cmd_authenticate(netdev,
NL80211_AUTHTYPE_FT);
l_genl_msg_append_attrv(cmd_authenticate, NL80211_ATTR_IE, iov,
iov_len);
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_authenticate,
netdev_cmd_authenticate_ft_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(cmd_authenticate);
goto restore_snonce;
}
return;
restore_snonce:
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
static int netdev_ft_tx_associate(struct iovec *ft_iov, size_t n_ft_iov,
void *user_data)
{
struct netdev *netdev = user_data;
struct auth_proto *ap = netdev->ap;
struct handshake_state *hs = netdev->handshake;
struct l_genl_msg *msg;
struct iovec iov[64];
unsigned int n_iov = L_ARRAY_SIZE(iov);
unsigned int c_iov = 0;
enum mpdu_management_subtype subtype =
MPDU_MANAGEMENT_SUBTYPE_REASSOCIATION_REQUEST;
msg = netdev_build_cmd_associate_common(netdev);
c_iov = netdev_populate_common_ies(netdev, hs, msg, iov, n_iov, c_iov);
if (!L_WARN_ON(n_iov - c_iov < n_ft_iov)) {
memcpy(iov + c_iov, ft_iov, sizeof(*ft_iov) * n_ft_iov);
c_iov += n_ft_iov;
}
mpdu_sort_ies(subtype, iov, c_iov);
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN,
ap->prev_bssid);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, c_iov);
netdev->connect_cmd_id = l_genl_family_send(nl80211, msg,
netdev_cmd_ft_reassociate_cb,
netdev, NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(msg);
return -EIO;
}
return 0;
}
static void prepare_ft(struct netdev *netdev, const struct scan_bss *target_bss)
{
struct netdev_handshake_state *nhs;
/*
* We reuse the handshake_state object and reset what's needed.
* Could also create a new object and copy most of the state but
* we would end up doing more work.
*/
memcpy(netdev->prev_snonce, netdev->handshake->snonce, 32);
netdev->frequency = target_bss->frequency;
handshake_state_set_authenticator_address(netdev->handshake,
target_bss->addr);
if (target_bss->rsne)
handshake_state_set_authenticator_ie(netdev->handshake,
target_bss->rsne);
memcpy(netdev->handshake->mde + 2, target_bss->mde, 3);
netdev->associated = false;
netdev->operational = false;
netdev->in_ft = true;
/*
* Cancel commands that could be running because of EAPoL activity
* like re-keying, this way the callbacks for those commands don't
* have to check if failures resulted from the transition.
*/
nhs = l_container_of(netdev->handshake,
struct netdev_handshake_state, super);
/* reset key states just as we do in initialization */
nhs->complete = false;
nhs->ptk_installed = false;
nhs->gtk_installed = true;
nhs->igtk_installed = true;
if (nhs->group_new_key_cmd_id) {
l_genl_family_cancel(nl80211, nhs->group_new_key_cmd_id);
nhs->group_new_key_cmd_id = 0;
}
if (nhs->group_management_new_key_cmd_id) {
l_genl_family_cancel(nl80211,
nhs->group_management_new_key_cmd_id);
nhs->group_management_new_key_cmd_id = 0;
}
if (netdev->rekey_offload_cmd_id) {
l_genl_family_cancel(nl80211, netdev->rekey_offload_cmd_id);
netdev->rekey_offload_cmd_id = 0;
}
netdev_rssi_polling_update(netdev);
netdev_cqm_rssi_update(netdev);
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
}
}
static void netdev_ft_over_ds_auth_failed(struct netdev_ft_over_ds_info *info,
uint16_t status)
{
l_queue_remove(info->netdev->ft_ds_list, info);
ft_ds_info_free(&info->super);
}
struct ft_ds_finder {
const uint8_t *spa;
const uint8_t *aa;
};
static bool match_ft_ds_info(const void *a, const void *b)
{
const struct netdev_ft_over_ds_info *info = a;
const struct ft_ds_finder *finder = b;
if (memcmp(info->super.spa, finder->spa, 6))
return false;
if (memcmp(info->super.aa, finder->aa, 6))
return false;
return true;
}
2020-01-16 03:40:59 +01:00
static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
2020-01-16 03:40:59 +01:00
int rssi, void *user_data)
{
struct netdev *netdev = user_data;
struct netdev_ft_over_ds_info *info;
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
const uint8_t *aa;
const uint8_t *spa;
const uint8_t *ies;
size_t ies_len;
struct ft_ds_finder finder;
ret = ft_over_ds_parse_action_response(body, body_len, &spa, &aa,
&ies, &ies_len);
if (ret < 0)
return;
finder.spa = spa;
finder.aa = aa;
info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder);
if (!info)
return;
/* Lookup successful, now check the status code */
if (ret > 0) {
status_code = (uint16_t)ret;
goto ft_error;
}
ret = ft_over_ds_parse_action_ies(&info->super, netdev->handshake,
ies, ies_len);
if (ret < 0)
goto ft_error;
info->parsed = true;
return;
ft_error:
l_debug("FT-over-DS to "MAC" failed (%d)", MAC_STR(info->super.aa),
status_code);
netdev_ft_over_ds_auth_failed(info, status_code);
}
2020-01-16 03:40:59 +01:00
static void netdev_qos_map_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
2020-01-16 03:40:59 +01:00
int rssi, void *user_data)
{
2020-01-16 03:40:59 +01:00
struct netdev *netdev = user_data;
/* No point telling the kernel */
if (!netdev->connected)
return;
if (memcmp(netdev->handshake->aa, hdr->address_2, ETH_ALEN))
return;
if (body_len < 5)
return;
if (l_get_u8(body + 2) != IE_TYPE_QOS_MAP_SET)
return;
netdev_send_qos_map_set(netdev, body + 4, body_len - 4);
}
static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
if (!auth_proto_start(netdev->ap)) {
/* Restore original nonce */
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return true;
}
return false;
}
static const struct wiphy_radio_work_item_ops ft_work_ops = {
.do_work = netdev_ft_work_ready,
};
int netdev_fast_transition(struct netdev *netdev,
const struct scan_bss *target_bss,
const struct scan_bss *orig_bss,
netdev_connect_cb_t cb)
{
if (!netdev->operational)
return -ENOTCONN;
if (!netdev->handshake->mde || !target_bss->mde_present ||
l_get_le16(netdev->handshake->mde + 2) !=
l_get_le16(target_bss->mde))
return -EINVAL;
prepare_ft(netdev, target_bss);
handshake_state_new_snonce(netdev->handshake);
netdev->connect_cb = cb;
netdev->ap = ft_over_air_sm_new(netdev->handshake,
netdev_ft_tx_authenticate,
netdev_ft_tx_associate, netdev);
memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN);
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&ft_work_ops);
return 0;
}
int netdev_fast_transition_over_ds(struct netdev *netdev,
const struct scan_bss *target_bss,
const struct scan_bss *orig_bss,
netdev_connect_cb_t cb)
{
struct netdev_ft_over_ds_info *info;
struct ft_ds_finder finder;
if (!netdev->operational)
return -ENOTCONN;
if (!netdev->handshake->mde || !target_bss->mde_present ||
l_get_le16(netdev->handshake->mde + 2) !=
l_get_le16(target_bss->mde))
return -EINVAL;
finder.spa = netdev->addr;
finder.aa = target_bss->addr;
info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder);
if (!info || !info->parsed)
return -ENOENT;
prepare_ft(netdev, target_bss);
ft_over_ds_prepare_handshake(&info->super, netdev->handshake);
netdev->connect_cb = cb;
netdev->ap = ft_over_ds_sm_new(netdev->handshake,
netdev_ft_tx_associate,
netdev);
memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN);
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&ft_work_ops);
return 0;
}
static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev_ft_over_ds_info *info = user_data;
if (l_genl_msg_get_error(msg) < 0) {
l_error("Could not send CMD_FRAME for FT-over-DS");
netdev_ft_over_ds_auth_failed(info,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_ft_ds_info_free(struct ft_ds_info *ft)
{
struct netdev_ft_over_ds_info *info = l_container_of(ft,
struct netdev_ft_over_ds_info, super);
l_free(info);
}
int netdev_fast_transition_over_ds_action(struct netdev *netdev,
const struct scan_bss *target_bss)
{
struct netdev_ft_over_ds_info *info;
uint8_t ft_req[14];
struct handshake_state *hs = netdev->handshake;
struct iovec iovs[5];
uint8_t buf[512];
size_t len;
if (!netdev->operational)
return -ENOTCONN;
if (!netdev->handshake->mde || !target_bss->mde_present ||
l_get_le16(netdev->handshake->mde + 2) !=
l_get_le16(target_bss->mde))
return -EINVAL;
l_debug("");
info = l_new(struct netdev_ft_over_ds_info, 1);
info->netdev = netdev;
memcpy(info->super.spa, hs->spa, ETH_ALEN);
memcpy(info->super.aa, target_bss->addr, ETH_ALEN);
memcpy(info->super.mde, target_bss->mde, sizeof(info->super.mde));
l_getrandom(info->super.snonce, 32);
info->super.free = netdev_ft_ds_info_free;
ft_req[0] = 6; /* FT category */
ft_req[1] = 1; /* FT Request action */
memcpy(ft_req + 2, netdev->addr, 6);
memcpy(ft_req + 8, info->super.aa, 6);
iovs[0].iov_base = ft_req;
iovs[0].iov_len = sizeof(ft_req);
if (!ft_build_authenticate_ies(hs, info->super.snonce, buf, &len))
goto failed;
iovs[1].iov_base = buf;
iovs[1].iov_len = len;
iovs[2].iov_base = NULL;
if (!netdev->ft_ds_list)
netdev->ft_ds_list = l_queue_new();
l_queue_push_head(netdev->ft_ds_list, info);
netdev_send_action_framev(netdev, netdev->handshake->aa, iovs, 2,
netdev->frequency,
netdev_ft_request_cb,
info);
return 0;
failed:
l_free(info);
return -EIO;
}
static void netdev_preauth_cb(const uint8_t *pmk, void *user_data)
{
struct netdev_preauth_state *preauth = user_data;
netdev_preauthenticate_cb_t cb = preauth->cb;
preauth->cb = NULL;
cb(preauth->netdev,
pmk ? NETDEV_RESULT_OK : NETDEV_RESULT_HANDSHAKE_FAILED,
pmk, preauth->user_data);
}
int netdev_preauthenticate(struct netdev *netdev, struct scan_bss *target_bss,
netdev_preauthenticate_cb_t cb, void *user_data)
{
struct netdev_preauth_state *preauth;
if (!netdev->operational)
return -ENOTCONN;
preauth = l_new(struct netdev_preauth_state, 1);
if (!eapol_preauth_start(target_bss->addr, netdev->handshake,
netdev_preauth_cb, preauth,
netdev_preauth_destroy)) {
l_free(preauth);
return -EIO;
}
preauth->cb = cb;
preauth->user_data = user_data;
preauth->netdev = netdev;
return 0;
}
static void netdev_neighbor_report_req_cb(struct l_genl_msg *msg,
void *user_data)
{
struct netdev *netdev = user_data;
if (!netdev->neighbor_report_cb)
return;
if (l_genl_msg_get_error(msg) < 0) {
netdev->neighbor_report_cb(netdev, l_genl_msg_get_error(msg),
NULL, 0, netdev->user_data);
netdev->neighbor_report_cb = NULL;
l_timeout_remove(netdev->neighbor_report_timeout);
}
}
static void netdev_neighbor_report_timeout(struct l_timeout *timeout,
void *user_data)
{
struct netdev *netdev = user_data;
netdev->neighbor_report_cb(netdev, -ETIMEDOUT, NULL, 0,
netdev->user_data);
netdev->neighbor_report_cb = NULL;
l_timeout_remove(netdev->neighbor_report_timeout);
}
int netdev_neighbor_report_req(struct netdev *netdev,
netdev_neighbor_report_cb_t cb)
{
const uint8_t action_frame[] = {
0x05, /* Category: Radio Measurement */
0x04, /* Radio Measurement Action: Neighbor Report Request */
0x01, /* Dialog Token: a non-zero value (unused) */
};
if (netdev->neighbor_report_cb || !netdev->connected)
return -EBUSY;
if (!netdev_send_action_frame(netdev, netdev->handshake->aa,
action_frame, sizeof(action_frame),
netdev->frequency,
netdev_neighbor_report_req_cb,
netdev))
return -EIO;
netdev->neighbor_report_cb = cb;
/* Set a 3-second timeout */
netdev->neighbor_report_timeout =
l_timeout_create(3, netdev_neighbor_report_timeout,
netdev, NULL);
return 0;
}
2020-01-16 03:40:59 +01:00
static void netdev_neighbor_report_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
2020-01-16 03:40:59 +01:00
int rssi, void *user_data)
{
2020-01-16 03:40:59 +01:00
struct netdev *netdev = user_data;
if (body_len < 3) {
l_debug("Neighbor Report frame too short");
return;
}
if (!netdev->neighbor_report_cb)
return;
/*
* Don't use the dialog token (byte 3), return the first Neighbor
* Report Response received.
*
* Byte 1 is 0x05 for Radio Measurement, byte 2 is 0x05 for
* Neighbor Report.
*/
netdev->neighbor_report_cb(netdev, 0, body + 3, body_len - 3,
netdev->user_data);
netdev->neighbor_report_cb = NULL;
l_timeout_remove(netdev->neighbor_report_timeout);
}
static void netdev_sa_query_resp_cb(struct l_genl_msg *msg, void *user_data)
{
int err = l_genl_msg_get_error(msg);
const char *ext_error;
if (err >= 0)
return;
ext_error = l_genl_msg_get_extended_error(msg);
l_debug("error sending SA Query request: %s",
ext_error ? ext_error : strerror(-err));
}
2020-01-16 03:40:59 +01:00
static void netdev_sa_query_req_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
int rssi, void *user_data)
{
uint8_t sa_resp[4];
uint16_t transaction;
2020-01-16 03:40:59 +01:00
struct netdev *netdev = user_data;
if (body_len < 4) {
l_debug("SA Query request too short");
return;
}
if (!netdev->connected)
return;
/* only care about SA Queries from our connected AP */
if (memcmp(hdr->address_2, netdev->handshake->aa, 6))
return;
transaction = l_get_u16(body + 2);
sa_resp[0] = 0x08; /* SA Query */
sa_resp[1] = 0x01; /* Response */
memcpy(sa_resp + 2, &transaction, 2);
l_info("received SA Query request from "MAC", transaction=%u",
MAC_STR(hdr->address_2), transaction);
if (!netdev_send_action_frame(netdev, netdev->handshake->aa,
sa_resp, sizeof(sa_resp),
netdev->frequency,
netdev_sa_query_resp_cb, netdev)) {
l_error("error sending SA Query response");
return;
}
}
2020-01-16 03:40:59 +01:00
static void netdev_sa_query_resp_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
int rssi, void *user_data)
{
2020-01-16 03:40:59 +01:00
struct netdev *netdev = user_data;
if (body_len < 4) {
l_debug("SA Query frame too short");
return;
}
l_debug("SA Query src="MAC" dest="MAC" bssid="MAC" transaction=%u",
MAC_STR(hdr->address_2), MAC_STR(hdr->address_1),
MAC_STR(hdr->address_3), l_get_u16(body + 2));
if (!netdev->sa_query_timeout) {
l_debug("no SA Query request sent");
return;
}
/* check if this is from our connected BSS */
if (memcmp(hdr->address_2, netdev->handshake->aa, 6)) {
l_debug("received SA Query from non-connected AP");
return;
}
if (memcmp(body + 2, &netdev->sa_query_id, 2)) {
l_debug("SA Query transaction ID's did not match");
return;
}
l_info("SA Query response from connected BSS received, "
"keeping the connection active");
l_timeout_remove(netdev->sa_query_timeout);
netdev->sa_query_timeout = NULL;
}
static void netdev_sa_query_req_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
int err = l_genl_msg_get_error(msg);
const char *ext_error;
if (err >= 0)
return;
ext_error = l_genl_msg_get_extended_error(msg);
l_debug("error sending SA Query request: %s",
ext_error ? ext_error : strerror(-err));
l_timeout_remove(netdev->sa_query_timeout);
netdev->sa_query_timeout = NULL;
}
static void netdev_sa_query_timeout(struct l_timeout *timeout,
void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
l_info("SA Query timed out, connection is invalid. Disconnecting...");
l_timeout_remove(netdev->sa_query_timeout);
netdev->sa_query_timeout = NULL;
msg = netdev_build_cmd_disconnect(netdev,
MMPDU_REASON_CODE_PREV_AUTH_NOT_VALID);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg,
netdev_disconnect_cb, netdev, NULL);
}
static void netdev_unprot_disconnect_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
const struct mmpdu_header *hdr = NULL;
struct l_genl_attr attr;
uint16_t type;
uint16_t len;
const void *data;
uint8_t action_frame[4];
uint8_t reason_code;
if (!netdev->connected)
return;
/* ignore excessive disassociate requests */
if (netdev->sa_query_timeout)
return;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_FRAME:
hdr = mpdu_validate(data, len);
break;
}
}
/* check that ATTR_FRAME was actually included */
if (!hdr)
return;
/* get reason code, first byte of frame */
reason_code = l_get_u8(mmpdu_body(hdr));
l_info("disconnect event, src="MAC" dest="MAC" bssid="MAC" reason=%u",
MAC_STR(hdr->address_2), MAC_STR(hdr->address_1),
MAC_STR(hdr->address_3), reason_code);
if (memcmp(hdr->address_2, netdev->handshake->aa, 6)) {
l_debug("received invalid disassociate frame");
return;
}
if (reason_code != MMPDU_REASON_CODE_CLASS2_FRAME_FROM_NONAUTH_STA &&
reason_code !=
MMPDU_REASON_CODE_CLASS3_FRAME_FROM_NONASSOC_STA) {
l_debug("invalid reason code %u", reason_code);
return;
}
action_frame[0] = 0x08; /* Category: SA Query */
action_frame[1] = 0x00; /* SA Query Action: Request */
/* Transaction ID */
l_getrandom(action_frame + 2, 2);
if (!netdev_send_action_frame(netdev, netdev->handshake->aa,
action_frame, sizeof(action_frame),
netdev->frequency,
netdev_sa_query_req_cb, netdev)) {
l_error("error sending SA Query action frame");
return;
}
netdev->sa_query_id = l_get_u16(action_frame + 2);
netdev->sa_query_timeout = l_timeout_create(3,
netdev_sa_query_timeout, netdev, NULL);
}
static void netdev_station_event(struct l_genl_msg *msg,
struct netdev *netdev, bool added)
{
struct l_genl_attr attr;
uint16_t type;
uint16_t len;
const void *data;
const uint8_t *mac = NULL;
if (netdev_get_iftype(netdev) != NETDEV_IFTYPE_ADHOC)
return;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_MAC:
mac = data;
break;
}
}
if (!mac) {
l_error("%s station event did not include MAC attribute",
added ? "new" : "del");
return;
}
WATCHLIST_NOTIFY(&netdev->station_watches,
netdev_station_watch_func_t, netdev, mac, added);
}
static struct netdev *netdev_from_message(struct l_genl_msg *msg)
{
uint32_t ifindex;
if (nl80211_parse_attrs(msg, NL80211_ATTR_IFINDEX, &ifindex,
NL80211_ATTR_UNSPEC) < 0)
return NULL;
return netdev_find(ifindex);
}
static void netdev_scan_notify(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev;
netdev = netdev_from_message(msg);
if (!netdev)
return;
switch (l_genl_msg_get_command(msg)) {
case NL80211_CMD_NEW_SCAN_RESULTS:
netdev_new_scan_results_event(msg, netdev);
break;
}
}
static bool match_addr(const void *a, const void *b)
{
const struct scan_bss *bss = a;
return memcmp(bss->addr, b, 6) == 0;
}
static bool netdev_get_fw_scan_cb(int err, struct l_queue *bss_list,
const struct scan_freq_set *freqs,
void *user_data)
{
struct netdev *netdev = user_data;
struct scan_bss *bss = NULL;
/*
* If we happened to be disconnected prior to GET_SCAN coming back
* just bail out now. This disconnect should already have been handled.
*/
if (!netdev->connected)
return false;
if (err < 0) {
l_error("Failed to get scan after roam (%d)", err);
goto failed;
}
/*
* We don't actually need the entire list since we only provide
* station with the roamed BSS. We can remove the BSS we want and by
* returning false scan will keep ownership of the list.
*/
bss = l_queue_remove_if(bss_list, match_addr, netdev->handshake->aa);
if (!bss) {
l_error("Roam target BSS not found in scan results");
goto failed;
}
netdev->fw_roam_bss = bss;
handshake_state_set_authenticator_ie(netdev->handshake, bss->rsne);
if (is_offload(netdev->handshake)) {
netdev_connect_ok(netdev);
return false;
}
if (netdev->sm) {
if (!eapol_start(netdev->sm))
goto failed;
}
return false;
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
return false;
}
/*
* CMD_ROAM indicates that the driver has already roamed/associated with a new
* AP. This event is nearly identical to the CMD_CONNECT event which is why
* netdev_connect_event will handle all the parsing of IE's just as it does
* normally.
*
* Using GET_SCAN we can grab all the required scan_bss data, create that object
* and provide it to station.
*
* The current handshake/netdev_handshake objects are reused after being
* reset to allow eapol to happen again without it thinking this is a re-key.
*/
static void netdev_roam_event(struct l_genl_msg *msg, struct netdev *netdev)
{
struct netdev_handshake_state *nhs =
l_container_of(netdev->handshake,
struct netdev_handshake_state,
super);
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const uint8_t *mac = NULL;
l_debug("");
netdev->operational = false;
if (!l_genl_attr_init(&attr, msg))
goto failed;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_MAC:
mac = data;
break;
case NL80211_ATTR_REQ_IE:
parse_request_ies(netdev, data, len);
break;
}
}
if (!mac) {
l_error("Failed to parse ATTR_MAC from CMD_ROAM");
goto failed;
}
/* Handshake completed in firmware, just get the roamed BSS */
if (is_offload(netdev->handshake))
goto get_fw_scan;
/* Reset handshake state */
nhs->complete = false;
nhs->ptk_installed = false;
nhs->gtk_installed = true;
nhs->igtk_installed = true;
netdev->handshake->ptk_complete = false;
get_fw_scan:
handshake_state_set_authenticator_address(netdev->handshake, mac);
if (!scan_get_firmware_scan(netdev->wdev_id, netdev_get_fw_scan_cb,
netdev, NULL))
goto failed;
if (netdev->event_filter)
netdev->event_filter(netdev, NETDEV_EVENT_ROAMING,
NULL, netdev->user_data);
return;
failed:
l_error("Failed to properly handle the ROAM event -- submit logs!");
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
}
static void netdev_channel_switch_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
uint32_t *freq = NULL;
l_debug("");
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_WIPHY_FREQ:
if (len != 4)
continue;
freq = (uint32_t *) data;
break;
}
}
if (!freq)
return;
l_debug("Channel switch event, frequency: %u", *freq);
netdev->frequency = *freq;
if (!netdev->event_filter)
return;
netdev->event_filter(netdev, NETDEV_EVENT_CHANNEL_SWITCHED, freq,
netdev->user_data);
}
static void netdev_mlme_notify(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = NULL;
uint8_t cmd;
cmd = l_genl_msg_get_command(msg);
l_debug("MLME notification %s(%u)", nl80211cmd_to_string(cmd), cmd);
netdev = netdev_from_message(msg);
if (!netdev)
return;
switch (cmd) {
case NL80211_CMD_AUTHENTICATE:
netdev_authenticate_event(msg, netdev);
break;
case NL80211_CMD_DEAUTHENTICATE:
netdev_deauthenticate_event(msg, netdev);
break;
case NL80211_CMD_ASSOCIATE:
netdev_associate_event(msg, netdev);
break;
case NL80211_CMD_ROAM:
netdev_roam_event(msg, netdev);
break;
case NL80211_CMD_CH_SWITCH_NOTIFY:
netdev_channel_switch_event(msg, netdev);
break;
2016-06-27 23:35:31 +02:00
case NL80211_CMD_CONNECT:
netdev_connect_event(msg, netdev);
break;
case NL80211_CMD_DISCONNECT:
netdev_disconnect_event(msg, netdev);
break;
case NL80211_CMD_NOTIFY_CQM:
netdev_cqm_event(msg, netdev);
break;
case NL80211_CMD_SET_REKEY_OFFLOAD:
netdev_rekey_offload_event(msg, netdev);
break;
case NL80211_CMD_UNPROT_DEAUTHENTICATE:
case NL80211_CMD_UNPROT_DISASSOCIATE:
netdev_unprot_disconnect_event(msg, netdev);
break;
case NL80211_CMD_NEW_STATION:
netdev_station_event(msg, netdev, true);
break;
case NL80211_CMD_DEL_STATION:
netdev_station_event(msg, netdev, false);
break;
}
}
static void netdev_pae_destroy(void *user_data)
{
struct netdev *netdev = user_data;
netdev->pae_io = NULL;
}
static bool netdev_pae_read(struct l_io *io, void *user_data)
{
int fd = l_io_get_fd(io);
struct sockaddr_ll sll;
socklen_t sll_len;
ssize_t bytes;
uint8_t frame[IEEE80211_MAX_DATA_LEN];
memset(&sll, 0, sizeof(sll));
sll_len = sizeof(sll);
bytes = recvfrom(fd, frame, sizeof(frame), 0,
(struct sockaddr *) &sll, &sll_len);
if (bytes <= 0) {
l_error("EAPoL read socket: %s", strerror(errno));
return false;
}
if (sll.sll_halen != ETH_ALEN)
return true;
__eapol_rx_packet(sll.sll_ifindex, sll.sll_addr,
ntohs(sll.sll_protocol), frame, bytes, false);
return true;
}
2018-03-13 21:27:20 +01:00
static void netdev_control_port_frame_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type;
uint16_t len;
const void *data;
const uint8_t *frame = NULL;
uint16_t frame_len = 0;
const uint8_t *src = NULL;
uint16_t proto = 0;
bool unencrypted = false;
l_debug("");
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_FRAME:
if (frame)
return;
frame = data;
frame_len = len;
break;
case NL80211_ATTR_MAC:
if (src)
return;
src = data;
break;
case NL80211_ATTR_CONTROL_PORT_ETHERTYPE:
if (len != sizeof(proto))
return;
proto = *((const uint16_t *) data);
break;
case NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT:
unencrypted = true;
break;
}
}
if (!src || !frame || !proto)
return;
__eapol_rx_packet(netdev->index, src, proto,
frame, frame_len, unencrypted);
}
2018-05-01 20:31:20 +02:00
static struct l_genl_msg *netdev_build_control_port_frame(struct netdev *netdev,
const uint8_t *to,
uint16_t proto,
bool unencrypted,
const void *body,
size_t body_len)
{
struct l_genl_msg *msg;
msg = l_genl_msg_new_sized(NL80211_CMD_CONTROL_PORT_FRAME,
128 + body_len);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME, body_len, body);
l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, 2,
&proto);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, to);
if (unencrypted)
l_genl_msg_append_attr(msg,
NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT, 0, NULL);
return msg;
}
static void netdev_control_port_frame_cb(struct l_genl_msg *msg,
void *user_data)
{
int err = l_genl_msg_get_error(msg);
const char *ext_error;
2018-05-01 20:31:20 +02:00
if (err >= 0)
return;
2018-05-01 20:31:20 +02:00
ext_error = l_genl_msg_get_extended_error(msg);
l_error("CMD_CONTROL_PORT failed: %s",
ext_error ? ext_error : strerror(-err));
2018-05-01 20:31:20 +02:00
}
static int netdev_control_port_write_pae(struct netdev *netdev,
const uint8_t *dest,
uint16_t proto,
const struct eapol_frame *ef,
bool noencrypt)
{
int fd = l_io_get_fd(netdev->pae_io);
struct sockaddr_ll sll;
size_t frame_size = sizeof(struct eapol_header) +
L_BE16_TO_CPU(ef->header.packet_len);
ssize_t r;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = netdev->index;
sll.sll_protocol = htons(proto);
sll.sll_halen = ETH_ALEN;
memcpy(sll.sll_addr, dest, ETH_ALEN);
r = sendto(fd, ef, frame_size, 0,
(struct sockaddr *) &sll, sizeof(sll));
if (r < 0)
l_error("EAPoL write socket: %s", strerror(errno));
return r;
}
static int netdev_control_port_frame(uint32_t ifindex,
const uint8_t *dest, uint16_t proto,
const struct eapol_frame *ef,
bool noencrypt,
void *user_data)
{
struct l_genl_msg *msg;
struct netdev *netdev;
size_t frame_size;
netdev = netdev_find(ifindex);
if (!netdev)
return -ENOENT;
frame_size = sizeof(struct eapol_header) +
L_BE16_TO_CPU(ef->header.packet_len);
if (!netdev->pae_over_nl80211)
2018-05-01 20:31:20 +02:00
return netdev_control_port_write_pae(netdev, dest, proto,
ef, noencrypt);
msg = netdev_build_control_port_frame(netdev, dest, proto, noencrypt,
ef, frame_size);
if (!msg)
return -ENOMEM;
if (!l_genl_family_send(nl80211, msg, netdev_control_port_frame_cb,
netdev, NULL)) {
l_genl_msg_unref(msg);
return -EINVAL;
}
return 0;
}
static void netdev_unicast_notify(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = NULL;
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
uint8_t cmd;
cmd = l_genl_msg_get_command(msg);
if (!cmd)
return;
l_debug("Unicast notification %u", cmd);
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_IFINDEX:
if (len != sizeof(uint32_t)) {
l_warn("Invalid interface index attribute");
return;
}
netdev = netdev_find(*((uint32_t *) data));
break;
}
}
if (!netdev)
return;
switch (cmd) {
2018-03-13 21:27:20 +01:00
case NL80211_CMD_CONTROL_PORT_FRAME:
netdev_control_port_frame_event(msg, netdev);
break;
}
}
int netdev_set_rssi_report_levels(struct netdev *netdev, const int8_t *levels,
size_t levels_num)
{
struct l_genl_msg *cmd_set_cqm;
2019-08-04 08:16:56 +02:00
l_debug("ifindex: %d, num_levels: %zu", netdev->index, levels_num);
if (levels_num > L_ARRAY_SIZE(netdev->rssi_levels))
return -ENOSPC;
if (!wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CQM_RSSI_LIST))
goto done;
cmd_set_cqm = netdev_build_cmd_cqm_rssi_update(netdev, levels,
levels_num);
if (!cmd_set_cqm)
return -EINVAL;
if (!l_genl_family_send(nl80211, cmd_set_cqm, netdev_cmd_set_cqm_cb,
NULL, NULL)) {
l_genl_msg_unref(cmd_set_cqm);
return -EIO;
}
done:
if (levels_num)
memcpy(netdev->rssi_levels, levels, levels_num);
netdev->rssi_levels_num = levels_num;
netdev_rssi_level_init(netdev);
netdev_rssi_polling_update(netdev);
return 0;
}
static void netdev_get_station_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_attr attr, nested;
uint16_t type, len;
const void *data;
struct diagnostic_station_info info;
netdev->get_station_cmd_id = 0;
if (!l_genl_attr_init(&attr, msg))
goto parse_error;
memset(&info, 0, sizeof(info));
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_STA_INFO:
if (!l_genl_attr_recurse(&attr, &nested))
goto parse_error;
if (!netdev_parse_sta_info(&nested, &info))
goto parse_error;
break;
case NL80211_ATTR_MAC:
if (len != 6)
goto parse_error;
memcpy(info.addr, data, 6);
break;
}
}
if (netdev->get_station_cb)
netdev->get_station_cb(&info, netdev->get_station_data);
return;
parse_error:
if (netdev->get_station_cb)
netdev->get_station_cb(NULL, netdev->get_station_data);
}
static void netdev_get_station_destroy(void *user_data)
{
struct netdev *netdev = user_data;
netdev->get_station_cmd_id = 0;
if (netdev->get_station_destroy)
netdev->get_station_destroy(netdev->get_station_data);
}
int netdev_get_station(struct netdev *netdev, const uint8_t *mac,
netdev_get_station_cb_t cb, void *user_data,
netdev_destroy_func_t destroy)
{
struct l_genl_msg *msg;
if (netdev->get_station_cmd_id)
return -EBUSY;
msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, mac);
netdev->get_station_cmd_id = l_genl_family_send(nl80211, msg,
netdev_get_station_cb, netdev,
netdev_get_station_destroy);
if (!netdev->get_station_cmd_id) {
l_genl_msg_unref(msg);
return -EIO;
}
netdev->get_station_cb = cb;
netdev->get_station_data = user_data;
netdev->get_station_destroy = destroy;
return 0;
}
int netdev_get_current_station(struct netdev *netdev,
netdev_get_station_cb_t cb, void *user_data,
netdev_destroy_func_t destroy)
{
if (!netdev->handshake)
return -ENOTCONN;
return netdev_get_station(netdev, netdev->handshake->aa, cb,
user_data, destroy);
}
int netdev_get_all_stations(struct netdev *netdev, netdev_get_station_cb_t cb,
void *user_data, netdev_destroy_func_t destroy)
{
struct l_genl_msg *msg;
if (netdev->get_station_cmd_id)
return -EBUSY;
msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
netdev->get_station_cmd_id = l_genl_family_dump(nl80211, msg,
netdev_get_station_cb, netdev,
netdev_get_station_destroy);
if (!netdev->get_station_cmd_id) {
l_genl_msg_unref(msg);
return -EIO;
}
netdev->get_station_cb = cb;
netdev->get_station_data = user_data;
netdev->get_station_destroy = destroy;
return 0;
}
static void netdev_add_station_frame_watches(struct netdev *netdev)
{
static const uint8_t action_neighbor_report_prefix[2] = { 0x05, 0x05 };
static const uint8_t action_sa_query_resp_prefix[2] = { 0x08, 0x01 };
static const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
static const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
static const uint8_t action_qos_map_prefix[] = { 0x01, 0x04 };
uint64_t wdev = netdev->wdev_id;
/* Subscribe to Management -> Action -> RM -> Neighbor Report frames */
frame_watch_add(wdev, 0, 0x00d0, action_neighbor_report_prefix,
sizeof(action_neighbor_report_prefix),
netdev_neighbor_report_frame_event, netdev, NULL);
frame_watch_add(wdev, 0, 0x00d0, action_sa_query_resp_prefix,
sizeof(action_sa_query_resp_prefix),
netdev_sa_query_resp_frame_event, netdev, NULL);
frame_watch_add(wdev, 0, 0x00d0, action_sa_query_req_prefix,
sizeof(action_sa_query_req_prefix),
netdev_sa_query_req_frame_event, netdev, NULL);
frame_watch_add(wdev, 0, 0x00d0, action_ft_response_prefix,
sizeof(action_ft_response_prefix),
netdev_ft_response_frame_event, netdev, NULL);
if (wiphy_supports_qos_set_map(netdev->wiphy))
frame_watch_add(wdev, 0, 0x00d0, action_qos_map_prefix,
sizeof(action_qos_map_prefix),
netdev_qos_map_frame_event, netdev, NULL);
}
static void netdev_setup_interface(struct netdev *netdev)
{
switch (netdev->type) {
case NL80211_IFTYPE_STATION:
netdev_add_station_frame_watches(netdev);
break;
default:
break;
}
}
static void netdev_set_interface_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
uint32_t iftype;
uint64_t wdev_id;
if (nl80211_parse_attrs(msg, NL80211_ATTR_IFTYPE, &iftype,
NL80211_ATTR_WDEV, &wdev_id,
NL80211_ATTR_UNSPEC) < 0)
return;
if (iftype == netdev->type)
return;
l_debug("Interface type changed from %s to %s",
netdev_iftype_to_string(netdev->type),
netdev_iftype_to_string(iftype));
netdev->type = iftype;
frame_watch_wdev_remove(wdev_id);
netdev_setup_interface(netdev);
2021-04-16 23:56:13 +02:00
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_IFTYPE_CHANGE);
}
static void netdev_config_notify(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev;
netdev = netdev_from_message(msg);
if (!netdev)
return;
switch (l_genl_msg_get_command(msg)) {
case NL80211_CMD_SET_INTERFACE:
netdev_set_interface_event(msg, netdev);
break;
}
}
static struct l_genl_msg *netdev_build_cmd_set_interface(struct netdev *netdev,
uint32_t iftype)
{
struct l_genl_msg *msg =
l_genl_msg_new_sized(NL80211_CMD_SET_INTERFACE, 32);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFTYPE, 4, &iftype);
return msg;
}
struct netdev_set_iftype_request {
netdev_command_cb_t cb;
void *user_data;
netdev_destroy_func_t destroy;
uint32_t pending_type;
uint32_t ref;
struct netdev *netdev;
bool bring_up;
};
static void netdev_set_iftype_request_destroy(void *user_data)
{
struct netdev_set_iftype_request *req = user_data;
struct netdev *netdev = req->netdev;
req->ref--;
if (req->ref)
return;
netdev->set_powered_cmd_id = 0;
netdev->set_interface_cmd_id = 0;
if (req->destroy)
req->destroy(req->user_data);
l_free(req);
}
static void netdev_set_iftype_up_cb(int error, uint16_t type,
const void *data,
uint32_t len, void *user_data)
{
struct netdev_set_iftype_request *req = user_data;
struct netdev *netdev = req->netdev;
if (req->cb)
req->cb(netdev, error, req->user_data);
}
static void netdev_set_iftype_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev_set_iftype_request *req = user_data;
struct netdev *netdev = req->netdev;
int error = l_genl_msg_get_error(msg);
if (error != 0)
goto done;
/* If the netdev was down originally, we're done */
if (!req->bring_up)
goto done;
netdev->set_powered_cmd_id =
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, netdev->index, true,
netdev_set_iftype_up_cb, req,
netdev_set_iftype_request_destroy);
if (!netdev->set_powered_cmd_id) {
error = -EIO;
goto done;
}
req->ref++;
netdev->set_interface_cmd_id = 0;
return;
done:
if (req->cb)
req->cb(netdev, error, req->user_data);
}
static void netdev_set_iftype_down_cb(int error, uint16_t type,
const void *data,
uint32_t len, void *user_data)
{
struct netdev_set_iftype_request *req = user_data;
struct netdev *netdev = req->netdev;
struct l_genl_msg *msg;
if (error != 0)
goto error;
msg = netdev_build_cmd_set_interface(netdev, req->pending_type);
netdev->set_interface_cmd_id =
l_genl_family_send(nl80211, msg, netdev_set_iftype_cb, req,
netdev_set_iftype_request_destroy);
if (!netdev->set_interface_cmd_id) {
l_genl_msg_unref(msg);
error = -EIO;
goto error;
}
req->ref++;
netdev->set_powered_cmd_id = 0;
return;
error:
if (req->cb)
req->cb(netdev, error, req->user_data);
}
int netdev_set_iftype(struct netdev *netdev, enum netdev_iftype type,
netdev_command_cb_t cb, void *user_data,
netdev_destroy_func_t destroy)
{
uint32_t iftype;
struct netdev_set_iftype_request *req;
switch (type) {
case NETDEV_IFTYPE_AP:
iftype = NL80211_IFTYPE_AP;
break;
case NETDEV_IFTYPE_ADHOC:
iftype = NL80211_IFTYPE_ADHOC;
break;
case NETDEV_IFTYPE_STATION:
iftype = NL80211_IFTYPE_STATION;
break;
default:
l_error("unsupported iftype %u", type);
return -EINVAL;
}
if (netdev->set_powered_cmd_id ||
netdev->set_interface_cmd_id)
return -EBUSY;
req = l_new(struct netdev_set_iftype_request, 1);
req->cb = cb;
req->user_data = user_data;
req->destroy = destroy;
req->pending_type = iftype;
req->netdev = netdev;
req->ref = 1;
req->bring_up = netdev_get_is_up(netdev);
if (!req->bring_up) {
struct l_genl_msg *msg =
netdev_build_cmd_set_interface(netdev, iftype);
netdev->set_interface_cmd_id =
l_genl_family_send(nl80211, msg,
netdev_set_iftype_cb, req,
netdev_set_iftype_request_destroy);
if (netdev->set_interface_cmd_id)
return 0;
l_genl_msg_unref(msg);
} else {
netdev->set_powered_cmd_id =
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, netdev->index, false,
netdev_set_iftype_down_cb, req,
netdev_set_iftype_request_destroy);
if (netdev->set_powered_cmd_id)
return 0;
}
l_free(req);
return -EIO;
}
static void netdev_bridge_port_event(const struct ifinfomsg *ifi, int bytes,
bool added)
{
struct netdev *netdev;
struct rtattr *attr;
uint32_t master = 0;
netdev = netdev_find(ifi->ifi_index);
if (!netdev)
return;
for (attr = IFLA_RTA(ifi); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFLA_MASTER:
memcpy(&master, RTA_DATA(attr), sizeof(master));
break;
}
}
l_debug("netdev: %d %s bridge: %d", ifi->ifi_index,
(added ? "added to" : "removed from"), master);
}
struct set_4addr_cb_data {
struct netdev *netdev;
bool value;
netdev_command_cb_t callback;
void *user_data;
netdev_destroy_func_t destroy;
};
static void netdev_set_4addr_cb(struct l_genl_msg *msg, void *user_data)
{
struct set_4addr_cb_data *cb_data = user_data;
int error = l_genl_msg_get_error(msg);
if (!cb_data)
return;
/* cache the value that has just been set */
if (!error)
cb_data->netdev->use_4addr = cb_data->value;
cb_data->callback(cb_data->netdev, error, cb_data->user_data);
}
static void netdev_set_4addr_destroy(void *user_data)
{
struct set_4addr_cb_data *cb_data = user_data;
if (!cb_data)
return;
if (cb_data->destroy)
cb_data->destroy(cb_data->user_data);
l_free(cb_data);
}
int netdev_set_4addr(struct netdev *netdev, bool use_4addr,
netdev_command_cb_t cb, void *user_data,
netdev_destroy_func_t destroy)
{
struct set_4addr_cb_data *cb_data = NULL;
uint8_t attr_4addr = (use_4addr ? 1 : 0);
struct l_genl_msg *msg;
l_debug("netdev: %d use_4addr: %d", netdev->index, use_4addr);
msg = l_genl_msg_new_sized(NL80211_CMD_SET_INTERFACE, 32);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_4ADDR, 1, &attr_4addr);
if (cb) {
cb_data = l_new(struct set_4addr_cb_data, 1);
cb_data->netdev = netdev;
cb_data->value = use_4addr;
cb_data->callback = cb;
cb_data->user_data = user_data;
cb_data->destroy = destroy;
}
if (!l_genl_family_send(nl80211, msg, netdev_set_4addr_cb, cb_data,
netdev_set_4addr_destroy)) {
l_error("CMD_SET_INTERFACE (4addr) failed");
l_genl_msg_unref(msg);
l_free(cb_data);
return -EIO;
}
return 0;
}
bool netdev_get_4addr(struct netdev *netdev)
{
return netdev->use_4addr;
}
2016-06-20 12:42:03 +02:00
static void netdev_newlink_notify(const struct ifinfomsg *ifi, int bytes)
{
struct netdev *netdev;
2016-07-13 04:26:27 +02:00
bool old_up, new_up;
2016-07-14 02:38:05 +02:00
char old_name[IFNAMSIZ];
uint8_t old_addr[ETH_ALEN];
2016-07-14 02:38:05 +02:00
struct rtattr *attr;
2016-06-20 12:42:03 +02:00
if (ifi->ifi_family == AF_BRIDGE) {
netdev_bridge_port_event(ifi, bytes, true);
return;
}
2016-06-20 12:42:03 +02:00
netdev = netdev_find(ifi->ifi_index);
if (!netdev)
return;
2016-07-13 04:26:27 +02:00
old_up = netdev_get_is_up(netdev);
2016-07-14 02:38:05 +02:00
strcpy(old_name, netdev->name);
memcpy(old_addr, netdev->addr, ETH_ALEN);
2016-07-13 04:26:27 +02:00
netdev->ifi_flags = ifi->ifi_flags;
2016-06-20 12:42:03 +02:00
2016-07-14 02:38:05 +02:00
for (attr = IFLA_RTA(ifi); RTA_OK(attr, bytes);
attr = RTA_NEXT(attr, bytes)) {
switch (attr->rta_type) {
case IFLA_IFNAME:
strcpy(netdev->name, RTA_DATA(attr));
break;
case IFLA_ADDRESS:
if (RTA_PAYLOAD(attr) < ETH_ALEN)
break;
2016-07-14 02:38:05 +02:00
memcpy(netdev->addr, RTA_DATA(attr), ETH_ALEN);
break;
}
2016-07-14 02:38:05 +02:00
}
if (!netdev->events_ready) /* Did we send NETDEV_WATCH_EVENT_NEW yet? */
return;
2016-07-13 04:26:27 +02:00
new_up = netdev_get_is_up(netdev);
/*
* If mac_change_cmd_id is set we are in the process of changing the
* MAC address and this event is a result of powering down/up. In this
* case we do not want to emit a netdev DOWN/UP event as this would
* cause other modules to behave as such. We do, however, want to emit
* address changes so other modules get the new MAC address updated.
*/
if (old_up != new_up && !netdev->mac_change_cmd_id)
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, new_up ? NETDEV_WATCH_EVENT_UP :
NETDEV_WATCH_EVENT_DOWN);
2016-07-14 02:38:05 +02:00
if (strcmp(old_name, netdev->name))
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_NAME_CHANGE);
2016-07-14 02:38:05 +02:00
if (memcmp(old_addr, netdev->addr, ETH_ALEN))
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_ADDRESS_CHANGE);
2016-06-20 12:42:03 +02:00
}
2016-07-01 21:49:34 +02:00
static void netdev_dellink_notify(const struct ifinfomsg *ifi, int bytes)
{
struct netdev *netdev;
if (ifi->ifi_family == AF_BRIDGE) {
netdev_bridge_port_event(ifi, bytes, false);
return;
}
2016-07-01 21:49:34 +02:00
netdev = l_queue_remove_if(netdev_list, netdev_match,
L_UINT_TO_PTR(ifi->ifi_index));
if (!netdev)
return;
netdev_free(netdev);
}
static void netdev_initial_up_cb(int error, uint16_t type, const void *data,
uint32_t len, void *user_data)
{
struct netdev *netdev = user_data;
netdev->set_powered_cmd_id = 0;
if (!error)
netdev->ifi_flags |= IFF_UP;
else {
l_error("Error bringing interface %i up: %s", netdev->index,
strerror(-error));
if (error != -ERFKILL)
return;
}
2020-02-15 10:48:39 +01:00
l_rtnl_set_linkmode_and_operstate(rtnl, netdev->index,
IF_LINK_MODE_DORMANT, IF_OPER_DOWN,
netdev_operstate_cb,
L_UINT_TO_PTR(netdev->index), NULL);
/*
* we don't know the initial status of the 4addr property on this
* netdev, therefore we set it to zero by default.
*/
netdev_set_4addr(netdev, netdev->use_4addr, NULL, NULL, NULL);
l_debug("Interface %i initialized", netdev->index);
scan_wdev_add(netdev->wdev_id);
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_NEW);
netdev->events_ready = true;
}
static bool netdev_check_set_mac(struct netdev *netdev)
{
if (l_memeqzero(netdev->set_mac_once, 6))
return false;
l_debug("Setting initial address on ifindex: %d to: " MAC,
netdev->index, MAC_STR(netdev->set_mac_once));
netdev->set_powered_cmd_id =
l_rtnl_set_mac(rtnl, netdev->index, netdev->set_mac_once, true,
netdev_initial_up_cb, netdev, NULL);
memset(netdev->set_mac_once, 0, 6);
return true;
}
static void netdev_initial_down_cb(int error, uint16_t type, const void *data,
uint32_t len, void *user_data)
{
struct netdev *netdev = user_data;
if (!error)
netdev->ifi_flags &= ~IFF_UP;
else {
l_error("Error taking interface %i down: %s", netdev->index,
strerror(-error));
netdev->set_powered_cmd_id = 0;
return;
}
if (netdev_check_set_mac(netdev))
return;
netdev->set_powered_cmd_id =
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, netdev->index, true,
netdev_initial_up_cb, netdev, NULL);
}
2016-06-20 12:42:03 +02:00
static void netdev_getlink_cb(int error, uint16_t type, const void *data,
uint32_t len, void *user_data)
{
const struct ifinfomsg *ifi = data;
unsigned int bytes;
struct netdev *netdev;
l_netlink_command_func_t cb;
bool powered;
2016-06-20 12:42:03 +02:00
if (error != 0) {
l_error("RTM_GETLINK error %i: %s", error, strerror(-error));
return;
}
if (ifi->ifi_type != ARPHRD_ETHER || type != RTM_NEWLINK) {
l_debug("Non-ethernet address or not newlink message -- "
"ifi_type: %i, type: %i", ifi->ifi_type, type);
2016-06-20 12:42:03 +02:00
return;
}
netdev = netdev_find(ifi->ifi_index);
if (!netdev)
return;
2016-06-20 12:42:03 +02:00
bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg));
netdev_newlink_notify(ifi, bytes);
/*
* If the interface is UP, reset it to ensure a clean state.
* Otherwise, if we need to set a random mac, do so. If not, just
* bring the interface UP.
*/
powered = netdev_get_is_up(netdev);
if (!powered && netdev_check_set_mac(netdev))
return;
cb = powered ? netdev_initial_down_cb : netdev_initial_up_cb;
netdev->set_powered_cmd_id =
2020-02-15 10:48:39 +01:00
l_rtnl_set_powered(rtnl, ifi->ifi_index, !powered, cb, netdev,
NULL);
2016-06-20 12:42:03 +02:00
}
static struct l_io *pae_open(uint32_t ifindex)
{
/*
* BPF filter to match skb->dev->type == 1 (ARPHRD_ETHER) and
* match skb->protocol == 0x888e (PAE) or 0x88c7 (preauthentication).
*/
struct sock_filter pae_filter[] = {
{ 0x20, 0, 0, 0xfffff008 }, /* ld #ifidx */
{ 0x15, 0, 6, 0x00000000 }, /* jne #0, drop */
{ 0x28, 0, 0, 0xfffff01c }, /* ldh #hatype */
{ 0x15, 0, 4, 0x00000001 }, /* jne #1, drop */
{ 0x28, 0, 0, 0xfffff000 }, /* ldh #proto */
{ 0x15, 1, 0, 0x0000888e }, /* je #0x888e, keep */
{ 0x15, 0, 1, 0x000088c7 }, /* jne #0x88c7, drop */
{ 0x06, 0, 0, 0xffffffff }, /* keep: ret #-1 */
{ 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */
};
const struct sock_fprog pae_fprog = {
.len = L_ARRAY_SIZE(pae_filter),
.filter = pae_filter
};
struct l_io *io;
int fd;
fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
htons(ETH_P_ALL));
if (fd < 0)
return NULL;
/*
* Here we modify the k value in the BPF program above to match the
* given ifindex. We do it this way instead of using bind to attach
* to a specific interface index to avoid having to re-open the fd
* whenever the device is powered down / up
*/
pae_filter[1].k = ifindex;
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
&pae_fprog, sizeof(pae_fprog)) < 0)
goto error;
io = l_io_new(fd);
l_io_set_close_on_destroy(io, true);
return io;
error:
close(fd);
return NULL;
}
struct netdev *netdev_create_from_genl(struct l_genl_msg *msg,
const uint8_t *set_mac)
{
2019-12-17 23:57:21 +01:00
const char *ifname;
const uint8_t *ifaddr;
uint32_t ifindex;
uint32_t iftype;
uint64_t wdev;
uint32_t wiphy_id;
struct netdev *netdev;
struct wiphy *wiphy = NULL;
2016-06-20 12:42:03 +02:00
struct ifinfomsg *rtmmsg;
size_t bufsize;
struct l_io *pae_io = NULL;
2019-12-17 23:57:21 +01:00
if (nl80211_parse_attrs(msg, NL80211_ATTR_IFINDEX, &ifindex,
NL80211_ATTR_WDEV, &wdev,
NL80211_ATTR_IFNAME, &ifname,
NL80211_ATTR_WIPHY, &wiphy_id,
NL80211_ATTR_IFTYPE, &iftype,
NL80211_ATTR_MAC, &ifaddr,
NL80211_ATTR_UNSPEC) < 0) {
l_warn("Required attributes missing");
return NULL;
2016-07-19 23:03:26 +02:00
}
2019-12-17 23:57:21 +01:00
wiphy = wiphy_find(wiphy_id);
if (!wiphy) {
l_warn("No wiphy: %d", wiphy_id);
return NULL;
}
2019-12-17 23:57:21 +01:00
if (netdev_find(ifindex)) {
l_debug("Skipping duplicate netdev %s[%d]", ifname, ifindex);
return NULL;
}
if (!wiphy_has_ext_feature(wiphy,
NL80211_EXT_FEATURE_CONTROL_PORT_OVER_NL80211) ||
!pae_over_nl80211) {
2019-12-17 23:57:21 +01:00
pae_io = pae_open(ifindex);
if (!pae_io) {
l_error("Unable to open PAE interface");
return NULL;
}
}
netdev = l_new(struct netdev, 1);
2019-12-17 23:57:21 +01:00
netdev->index = ifindex;
netdev->wdev_id = wdev;
netdev->type = iftype;
netdev->rekey_offload_support = true;
memcpy(netdev->addr, ifaddr, sizeof(netdev->addr));
2019-12-17 23:57:21 +01:00
l_strlcpy(netdev->name, ifname, IFNAMSIZ);
netdev->wiphy = wiphy;
netdev->pae_over_nl80211 = pae_io == NULL;
if (set_mac)
memcpy(netdev->set_mac_once, set_mac, 6);
if (pae_io) {
netdev->pae_io = pae_io;
l_io_set_read_handler(netdev->pae_io, netdev_pae_read, netdev,
netdev_pae_destroy);
}
watchlist_init(&netdev->station_watches, NULL);
l_queue_push_tail(netdev_list, netdev);
2019-07-08 16:02:58 +02:00
l_debug("Created interface %s[%d %" PRIx64 "]", netdev->name,
netdev->index, netdev->wdev_id);
2016-06-20 12:42:03 +02:00
/* Query interface flags */
bufsize = NLMSG_ALIGN(sizeof(struct ifinfomsg));
2016-06-20 12:42:03 +02:00
rtmmsg = l_malloc(bufsize);
memset(rtmmsg, 0, bufsize);
rtmmsg->ifi_family = AF_UNSPEC;
2019-12-17 23:57:21 +01:00
rtmmsg->ifi_index = ifindex;
2016-06-20 12:42:03 +02:00
l_netlink_send(rtnl, RTM_GETLINK, 0, rtmmsg, bufsize,
2018-08-17 20:29:05 +02:00
netdev_getlink_cb, NULL, NULL);
2016-06-20 12:42:03 +02:00
l_free(rtmmsg);
netdev_setup_interface(netdev);
return netdev;
}
bool netdev_destroy(struct netdev *netdev)
{
if (!l_queue_remove(netdev_list, netdev))
return false;
netdev_free(netdev);
return true;
}
2016-06-20 12:42:03 +02:00
static void netdev_link_notify(uint16_t type, const void *data, uint32_t len,
void *user_data)
{
const struct ifinfomsg *ifi = data;
unsigned int bytes;
if (ifi->ifi_type != ARPHRD_ETHER)
return;
l_debug("event %u on ifindex %u", type, ifi->ifi_index);
2016-06-20 12:42:03 +02:00
bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg));
switch (type) {
case RTM_NEWLINK:
netdev_newlink_notify(ifi, bytes);
break;
2016-07-01 21:49:34 +02:00
case RTM_DELLINK:
netdev_dellink_notify(ifi, bytes);
break;
2016-06-20 12:42:03 +02:00
}
}
uint32_t netdev_station_watch_add(struct netdev *netdev,
netdev_station_watch_func_t func, void *user_data)
2016-06-20 12:42:04 +02:00
{
return watchlist_add(&netdev->station_watches, func, user_data, NULL);
2016-06-20 12:42:04 +02:00
}
bool netdev_station_watch_remove(struct netdev *netdev, uint32_t id)
2016-06-20 12:42:04 +02:00
{
return watchlist_remove(&netdev->station_watches, id);
2016-06-20 12:42:04 +02:00
}
uint32_t netdev_watch_add(netdev_watch_func_t func,
void *user_data, netdev_destroy_func_t destroy)
{
return watchlist_add(&netdev_watches, func, user_data, destroy);
}
bool netdev_watch_remove(uint32_t id)
{
return watchlist_remove(&netdev_watches, id);
}
static int netdev_init(void)
{
2019-05-18 00:05:52 +02:00
struct l_genl *genl = iwd_get_genl();
const struct l_settings *settings = iwd_get_config();
const char *rand_addr_str;
if (rtnl)
return -EALREADY;
2020-04-08 19:28:00 +02:00
rtnl = iwd_get_rtnl();
2016-06-20 12:42:03 +02:00
if (!l_netlink_register(rtnl, RTNLGRP_LINK,
netdev_link_notify, NULL, NULL)) {
l_error("Failed to register for RTNL link notifications");
goto fail_netlink;
}
nl80211 = l_genl_family_new(genl, NL80211_GENL_NAME);
if (!nl80211) {
l_error("Failed to obtain nl80211");
goto fail_netlink;
2016-06-20 12:42:03 +02:00
}
if (!l_settings_get_int(settings, "General", "RoamThreshold",
&LOW_SIGNAL_THRESHOLD))
LOW_SIGNAL_THRESHOLD = -70;
if (!l_settings_get_int(settings, "General", "RoamThreshold5G",
&LOW_SIGNAL_THRESHOLD_5GHZ))
LOW_SIGNAL_THRESHOLD_5GHZ = -76;
if (!l_settings_get_bool(settings, "General", "ControlPortOverNL80211",
&pae_over_nl80211))
pae_over_nl80211 = true;
rand_addr_str = l_settings_get_value(settings, "General",
"AddressRandomization");
if (rand_addr_str && !strcmp(rand_addr_str, "network"))
mac_per_ssid = true;
watchlist_init(&netdev_watches, NULL);
2016-06-01 22:35:26 +02:00
netdev_list = l_queue_new();
2018-08-18 06:23:19 +02:00
__handshake_set_install_tk_func(netdev_set_tk);
__handshake_set_install_gtk_func(netdev_set_gtk);
__handshake_set_install_igtk_func(netdev_set_igtk);
__eapol_set_rekey_offload_func(netdev_set_rekey_offload);
__eapol_set_tx_packet_func(netdev_control_port_frame);
__eapol_set_install_pmk_func(netdev_set_pmk);
2018-08-18 06:23:19 +02:00
2019-05-18 00:05:52 +02:00
unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME,
netdev_unicast_notify,
NULL, NULL);
if (!unicast_watch)
l_error("Registering for unicast notification failed");
if (!l_genl_family_register(nl80211, "mlme", netdev_mlme_notify,
NULL, NULL))
l_error("Registering for MLME notification failed");
if (!l_genl_family_register(nl80211, "scan", netdev_scan_notify,
NULL, NULL))
l_error("Registering for scan notifications failed");
if (!l_genl_family_register(nl80211, "config", netdev_config_notify,
NULL, NULL))
l_error("Registering for config notifications failed");
return 0;
fail_netlink:
rtnl = NULL;
return -EIO;
}
static void netdev_exit(void)
{
2019-05-18 00:05:52 +02:00
struct l_genl *genl = iwd_get_genl();
if (!rtnl)
2018-08-18 06:23:19 +02:00
return;
2019-05-18 00:05:52 +02:00
l_genl_remove_unicast_watch(genl, unicast_watch);
watchlist_destroy(&netdev_watches);
l_queue_destroy(netdev_list, netdev_free);
netdev_list = NULL;
l_genl_family_free(nl80211);
nl80211 = NULL;
rtnl = NULL;
}
void netdev_shutdown(void)
{
netdev: Fix re-entrancy bug in netdev_shutdown netdev_shutdown calls queue_destroy on the netdev_list, which in turn calls netdev_free. netdev_free invokes the watches to notify them about the netdev being removed. Those clients, or anything downstream can still invoke netdev_find. Unfortunately queue_destroy is not re-entrant safe, so netdev_find might return stale data. Fix that by using l_queue_peek_head / l_queue_pop_head instead. src/station.c:station_enter_state() Old State: connecting, new state: connected ^CTerminate src/netdev.c:netdev_free() Freeing netdev wlan1[6] src/device.c:device_free() Removing scan context for wdev 100000001 src/scan.c:scan_context_free() sc: 0x4ae9ca0 src/netdev.c:netdev_free() Freeing netdev wlan0[48] src/device.c:device_free() src/station.c:station_free() src/netconfig.c:netconfig_destroy() ==103174== Invalid read of size 8 ==103174== at 0x467AA9: l_queue_find (queue.c:346) ==103174== by 0x43ACFF: netconfig_reset (netconfig.c:1027) ==103174== by 0x43AFFC: netconfig_destroy (netconfig.c:1123) ==103174== by 0x414379: station_free (station.c:3369) ==103174== by 0x414379: station_destroy_interface (station.c:3466) ==103174== by 0x47C80C: interface_instance_free (dbus-service.c:510) ==103174== by 0x47C80C: _dbus_object_tree_remove_interface (dbus-service.c:1694) ==103174== by 0x47C99C: _dbus_object_tree_object_destroy (dbus-service.c:795) ==103174== by 0x409A87: netdev_free (netdev.c:770) ==103174== by 0x4677AE: l_queue_clear (queue.c:107) ==103174== by 0x4677F8: l_queue_destroy (queue.c:82) ==103174== by 0x40CDC1: netdev_shutdown (netdev.c:5089) ==103174== by 0x404736: iwd_shutdown (main.c:78) ==103174== by 0x404736: iwd_shutdown (main.c:65) ==103174== by 0x46BD61: handle_callback (signal.c:78) ==103174== by 0x46BD61: signalfd_read_cb (signal.c:104)
2021-01-29 21:53:00 +01:00
struct netdev *netdev;
if (!rtnl)
return;
l_queue_foreach(netdev_list, netdev_shutdown_one, NULL);
netdev: Fix re-entrancy bug in netdev_shutdown netdev_shutdown calls queue_destroy on the netdev_list, which in turn calls netdev_free. netdev_free invokes the watches to notify them about the netdev being removed. Those clients, or anything downstream can still invoke netdev_find. Unfortunately queue_destroy is not re-entrant safe, so netdev_find might return stale data. Fix that by using l_queue_peek_head / l_queue_pop_head instead. src/station.c:station_enter_state() Old State: connecting, new state: connected ^CTerminate src/netdev.c:netdev_free() Freeing netdev wlan1[6] src/device.c:device_free() Removing scan context for wdev 100000001 src/scan.c:scan_context_free() sc: 0x4ae9ca0 src/netdev.c:netdev_free() Freeing netdev wlan0[48] src/device.c:device_free() src/station.c:station_free() src/netconfig.c:netconfig_destroy() ==103174== Invalid read of size 8 ==103174== at 0x467AA9: l_queue_find (queue.c:346) ==103174== by 0x43ACFF: netconfig_reset (netconfig.c:1027) ==103174== by 0x43AFFC: netconfig_destroy (netconfig.c:1123) ==103174== by 0x414379: station_free (station.c:3369) ==103174== by 0x414379: station_destroy_interface (station.c:3466) ==103174== by 0x47C80C: interface_instance_free (dbus-service.c:510) ==103174== by 0x47C80C: _dbus_object_tree_remove_interface (dbus-service.c:1694) ==103174== by 0x47C99C: _dbus_object_tree_object_destroy (dbus-service.c:795) ==103174== by 0x409A87: netdev_free (netdev.c:770) ==103174== by 0x4677AE: l_queue_clear (queue.c:107) ==103174== by 0x4677F8: l_queue_destroy (queue.c:82) ==103174== by 0x40CDC1: netdev_shutdown (netdev.c:5089) ==103174== by 0x404736: iwd_shutdown (main.c:78) ==103174== by 0x404736: iwd_shutdown (main.c:65) ==103174== by 0x46BD61: handle_callback (signal.c:78) ==103174== by 0x46BD61: signalfd_read_cb (signal.c:104)
2021-01-29 21:53:00 +01:00
while ((netdev = l_queue_peek_head(netdev_list))) {
netdev_free(netdev);
l_queue_pop_head(netdev_list);
}
}
IWD_MODULE(netdev, netdev_init, netdev_exit);
IWD_MODULE_DEPENDS(netdev, eapol);