3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-11-06 03:59:22 +01:00
iwd/src/netdev.c

4767 lines
116 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 "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"
#include "src/rtnlutil.h"
#ifndef ENOTSUPP
#define ENOTSUPP 524
#endif
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;
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;
};
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;
uint32_t prev_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 sae_sm *sae_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;
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;
struct l_timeout *gas_timeout;
uint16_t sa_query_id;
uint8_t prev_bssid[ETH_ALEN];
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;
2016-06-20 12:42:04 +02:00
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;
struct watchlist frame_watches;
struct watchlist station_watches;
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
bool connected : 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 mac_randomize_once : 1;
bool events_ready : 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
};
struct netdev_frame_watch {
uint16_t frame_type;
uint8_t *prefix;
size_t prefix_len;
struct watchlist_item super;
};
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 void do_debug(const char *str, void *user_data)
{
const char *prefix = user_data;
l_info("%s%s", prefix, str);
}
/* 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;
}
}
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
* 'installed' upon initalization. If/When the gtk/igtk callback is
* 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
{
switch (netdev->type) {
case NL80211_IFTYPE_STATION:
return NETDEV_IFTYPE_STATION;
case NL80211_IFTYPE_AP:
return NETDEV_IFTYPE_AP;
case NL80211_IFTYPE_ADHOC:
return NETDEV_IFTYPE_ADHOC;
case NL80211_IFTYPE_P2P_CLIENT:
return NETDEV_IFTYPE_P2P_CLIENT;
case NL80211_IFTYPE_P2P_GO:
return NETDEV_IFTYPE_P2P_GO;
default:
/* can't really do much here */
l_error("unknown iftype %u", netdev->type);
return NETDEV_IFTYPE_STATION;
}
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)
{
2016-07-13 04:26:27 +02:00
return (netdev->ifi_flags & IFF_UP) != 0;
}
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 =
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 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;
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;
found = true;
break;
}
if (!found || !l_genl_attr_recurse(&attr, &nested))
goto done;
found = false;
while (l_genl_attr_next(&nested, &type, &len, &data)) {
if (type != NL80211_STA_INFO_SIGNAL_AVG)
continue;
if (len != 1)
continue;
found = true;
netdev->cur_rssi = *(const int8_t *) data;
break;
}
if (!found)
goto done;
/*
* 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_connect_free(struct netdev *netdev)
{
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->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: 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_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
}
}
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;
netdev->disconnect_cmd_id = 0;
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)
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_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);
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);
}
if (netdev->connected)
netdev_connect_free(netdev);
else 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->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->events_ready)
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DEL);
scan_wdev_remove(netdev->wdev_id);
watchlist_destroy(&netdev->frame_watches);
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))
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));
}
static void netdev_lost_beacon(struct netdev *netdev)
{
if (!netdev->connected)
return;
if (netdev->event_filter)
netdev->event_filter(netdev, NETDEV_EVENT_LOST_BEACON, NULL,
netdev->user_data);
}
/* Threshold RSSI for roaming to trigger, configurable in main.conf */
static int LOW_SIGNAL_THRESHOLD;
static void netdev_cqm_event_rssi_threshold(struct netdev *netdev,
uint32_t rssi_event)
{
int event;
if (!netdev->connected)
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;
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 < LOW_SIGNAL_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_BEACON_LOSS_EVENT:
netdev_lost_beacon(netdev);
break;
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)
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)
{
l_debug("");
}
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)
{
if (l_genl_msg_get_error(msg) < 0)
l_error("DEL_STATION failed: %i", l_genl_msg_get_error(msg));
}
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)
{
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->connect_cb) {
netdev->connect_cb(netdev, NETDEV_RESULT_OK, NULL,
netdev->user_data);
netdev->connect_cb = NULL;
}
netdev_rssi_polling_update(netdev);
}
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;
}
netdev->result = NETDEV_RESULT_KEY_SETTING_FAILED;
handshake_event(&nhs->super, HANDSHAKE_EVENT_SETTING_KEYS_FAILED, &err);
switch (netdev->type) {
case NL80211_IFTYPE_STATION:
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:
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: 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
netdev_connect_ok(nhs->netdev);
}
}
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 == -ENOTSUPP)
goto done;
if (err < 0) {
l_error("Set Station failed for ifindex %d", netdev->index);
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) {
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
l_error("New Key for Group Key failed for ifindex: %d",
netdev->index);
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) {
2016-10-28 23:49:54 +02:00
l_error("New Key for Group Mgmt failed for ifindex: %d",
netdev->index);
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;
}
2018-06-22 02:32:55 +02:00
static void netdev_set_gtk(struct handshake_state *hs, uint8_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);
}
2018-06-22 02:32:55 +02:00
static void netdev_set_igtk(struct handshake_state *hs, uint8_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;
}
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) {
l_error("New Key for Pairwise Key failed for ifindex: %d",
netdev->index);
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);
}
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");
}
}
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);
if (err < 0)
l_error("Could not set QoS Map in kernel: %d", err);
netdev->qos_map_cmd_id = 0;
}
/*
* 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
* frame comes in and not require userspace to forward it back... but thats 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);
}
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;
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;
}
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
/* AP Rejected the authenticate / associate */
if (!status_code || *status_code != 0)
goto error;
if (!ies)
goto process_resp_ies;
/*
* 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_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;
}
}
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;
}
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 (!frame)
goto auth_error;
if (netdev->ap) {
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;
goto auth_error;
} else
goto auth_error;
return;
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)
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;
goto assoc_failed;
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 (!frame)
goto assoc_failed;
if (netdev->ap) {
ret = auth_proto_rx_associate(netdev->ap, frame, frame_len);
if (ret == 0) {
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 we need to prevent the 4-way handshake from
* happening, and instead just wait for rekeys
*/
if (netdev->in_ft) {
eapol_sm_set_require_handshake(netdev->sm,
false);
netdev->in_ft = false;
}
return;
} else if (ret == -EAGAIN) {
/*
* Here to support OWE retries. OWE will retry
* internally, but a connect even 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;
/* Wait for connect event */
if (l_genl_msg_get_error(msg) >= 0) {
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);
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,
const uint8_t *addr)
{
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, addr);
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);
l_genl_msg_append_attr(msg, NL80211_ATTR_IE, hs->supplicant_ie[1] + 2,
hs->supplicant_ie);
return msg;
}
static void netdev_auth_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_AUTHENTICATE");
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
}
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,
netdev->handshake->aa);
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);
}
}
static void netdev_sae_tx_associate(void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
struct iovec iov[2];
int iov_elems = 0;
msg = netdev_build_cmd_associate_common(netdev);
iov[iov_elems].iov_base = netdev->handshake->supplicant_ie;
iov[iov_elems].iov_len = netdev->handshake->supplicant_ie[1] + 2;
iov_elems++;
if (netdev->handshake->mde) {
iov[iov_elems].iov_base = netdev->handshake->mde;
iov[iov_elems].iov_len = netdev->handshake->mde[1] + 2;
iov_elems++;
}
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_elems);
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,
netdev->handshake->aa);
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);
}
}
static void netdev_owe_tx_associate(struct iovec *ie_iov, size_t iov_len,
void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_associate_common(netdev);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, ie_iov, iov_len);
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,
netdev->handshake->aa);
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);
}
}
static void netdev_fils_tx_associate(struct iovec *iov, size_t iov_len,
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 l_genl_msg *msg;
msg = netdev_build_cmd_associate_common(netdev);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_len);
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 (!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)
{
2016-06-27 23:35:31 +02:00
uint32_t auth_type = NL80211_AUTHTYPE_OPEN_SYSTEM;
struct l_genl_msg *msg;
struct iovec iov[4 + num_vendor_ies];
int iov_elems = 0;
bool is_rsn = hs->supplicant_ie != NULL;
const uint8_t *extended_capabilities;
const uint8_t *rm_enabled_capabilities;
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);
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 (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);
iov[iov_elems].iov_base = (void *) hs->supplicant_ie;
iov[iov_elems].iov_len = hs->supplicant_ie[1] + 2;
iov_elems += 1;
}
if (netdev->pae_over_nl80211)
l_genl_msg_append_attr(msg,
NL80211_ATTR_CONTROL_PORT_OVER_NL80211,
0, NULL);
rm_enabled_capabilities =
wiphy_get_rm_enabled_capabilities(netdev->wiphy);
if (rm_enabled_capabilities && bss->capability & IE_BSS_CAP_RM) {
iov[iov_elems].iov_base = (void *) rm_enabled_capabilities;
iov[iov_elems].iov_len = rm_enabled_capabilities[1] + 2;
iov_elems += 1;
l_genl_msg_append_attr(msg, NL80211_ATTR_USE_RRM, 0, NULL);
}
if (hs->mde) {
iov[iov_elems].iov_base = (void *) hs->mde;
iov[iov_elems].iov_len = hs->mde[1] + 2;
iov_elems += 1;
}
/*
* This element should be added after MDE
* See 802.11-2016, Section 9.3.3.6
*/
extended_capabilities = wiphy_get_extended_capabilities(netdev->wiphy,
netdev->type);
iov[iov_elems].iov_base = (void *) extended_capabilities;
iov[iov_elems].iov_len = extended_capabilities[1] + 2;
iov_elems += 1;
if (vendor_ies) {
memcpy(iov + iov_elems, vendor_ies,
sizeof(*vendor_ies) * num_vendor_ies);
iov_elems += num_vendor_ies;
}
if (iov_elems)
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_elems);
return msg;
}
2016-09-14 03:50:24 +02:00
static int netdev_connect_common(struct netdev *netdev,
struct l_genl_msg *cmd_connect,
struct scan_bss *bss,
struct handshake_state *hs,
struct eapol_sm *sm,
2016-09-14 03:50:24 +02:00
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
if (cmd_connect) {
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_connect, 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) {
l_genl_msg_unref(cmd_connect);
return -EIO;
}
}
netdev->event_filter = event_filter;
netdev->connect_cb = cb;
netdev->user_data = user_data;
netdev->connected = true;
netdev->handshake = hs;
2016-06-28 23:58:17 +02:00
netdev->sm = sm;
netdev->frequency = bss->frequency;
netdev->cur_rssi_low = false; /* Gets udpated on the 1st CQM event */
netdev->cur_rssi = bss->signal_strength / 100;
netdev_rssi_level_init(netdev);
2016-06-28 23:58:17 +02:00
handshake_state_set_authenticator_address(hs, bss->addr);
handshake_state_set_supplicant_address(hs, netdev->addr);
if (!wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
handshake_state_set_no_rekey(hs, true);
auth_proto_start(netdev->ap);
return 0;
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,
2016-09-14 03:50:24 +02:00
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
struct l_genl_msg *cmd_connect = NULL;
struct eapol_sm *sm = NULL;
bool is_rsn = hs->supplicant_ie != NULL;
2016-09-14 03:50:24 +02:00
if (netdev->type != NL80211_IFTYPE_STATION &&
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
2016-09-14 03:50:24 +02:00
if (netdev->connected)
return -EISCONN;
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);
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:
cmd_connect = netdev_build_cmd_connect(netdev, bss, hs,
NULL, vendor_ies, num_vendor_ies);
2016-09-14 03:50:24 +02:00
if (!cmd_connect)
return -EINVAL;
if (is_rsn || hs->settings_8021x)
sm = eapol_sm_new(hs);
}
return netdev_connect_common(netdev, cmd_connect, bss, hs, sm,
2016-09-14 03:50:24 +02:00
event_filter, cb, user_data);
}
int netdev_disconnect(struct netdev *netdev,
netdev_disconnect_cb_t cb, void *user_data)
{
struct l_genl_msg *disconnect;
if (netdev->type != NL80211_IFTYPE_STATION &&
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
if (!netdev->connected)
return -ENOTCONN;
if (netdev->disconnect_cmd_id)
return -EINPROGRESS;
/* Only perform this if we haven't successfully fully associated yet */
if (!netdev->operational) {
if (netdev->connect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
netdev->connect_cmd_id = 0;
}
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
} else {
netdev_connect_free(netdev);
}
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;
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 l_genl_msg *cmd_connect;
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state;
struct handshake_state *old_hs;
struct eapol_sm *sm = NULL, *old_sm;
bool is_rsn = hs->supplicant_ie != NULL;
int err;
cmd_connect = netdev_build_cmd_connect(netdev, target_bss, hs,
orig_bss->addr, NULL, 0);
if (!cmd_connect)
return -EINVAL;
if (is_rsn)
sm = eapol_sm_new(hs);
old_sm = netdev->sm;
old_hs = netdev->handshake;
err = netdev_connect_common(netdev, cmd_connect, target_bss, hs, sm,
event_filter, cb, user_data);
if (err < 0)
return err;
memcpy(netdev->prev_bssid, orig_bss->addr, ETH_ALEN);
netdev->operational = false;
netdev_rssi_polling_update(netdev);
if (old_sm)
eapol_sm_free(old_sm);
if (old_hs)
handshake_state_free(old_hs);
return err;
}
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)
{
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, netdev, 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)
{
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);
}
/*
* Build an FT Authentication Request frame according to 12.5.2 / 12.5.4:
* RSN or non-RSN Over-the-air FT Protocol, with the IE contents
* according to 12.8.2: FT authentication sequence: contents of first message.
*/
static struct l_genl_msg *netdev_build_cmd_ft_authenticate(
struct netdev *netdev,
const struct handshake_state *hs)
{
uint32_t auth_type = NL80211_AUTHTYPE_FT;
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, hs->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_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_ft_authenticate(netdev,
netdev->handshake);
if (!cmd_authenticate)
goto restore_snonce;
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 void netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len,
void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_msg *msg;
msg = netdev_build_cmd_associate_common(netdev);
l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN,
netdev->prev_bssid);
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, ie_iov, iov_len);
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);
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
}
static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
if (l_genl_msg_get_error(msg) < 0) {
l_error("Could not send CMD_FRAME");
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_ft_response_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *user_data)
{
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
if (!netdev->ap || !netdev->in_ft)
return;
ret = auth_proto_rx_authenticate(netdev->ap, body, body_len);
if (ret < 0)
goto ft_error;
else if (ret > 0) {
status_code = (uint16_t)ret;
goto ft_error;
}
return;
ft_error:
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
status_code);
return;
}
static void netdev_qos_map_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *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 void netdev_ft_over_ds_tx_authenticate(struct iovec *iov,
size_t iov_len, void *user_data)
{
struct netdev *netdev = user_data;
uint8_t ft_req[14];
struct handshake_state *hs = netdev->handshake;
struct iovec iovs[iov_len + 1];
ft_req[0] = 6; /* FT category */
ft_req[1] = 1; /* FT Request action */
memcpy(ft_req + 2, netdev->addr, 6);
memcpy(ft_req + 8, hs->aa, 6);
iovs[0].iov_base = ft_req;
iovs[0].iov_len = sizeof(ft_req);
memcpy(iovs + 1, iov, sizeof(*iov) * iov_len);
netdev_send_action_framev(netdev, netdev->prev_bssid, iovs, iov_len + 1,
netdev->prev_frequency,
netdev_ft_request_cb);
}
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
bool over_air,
netdev_connect_cb_t cb)
{
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state *nhs;
int err = -EINVAL;
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;
/*
* 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_bssid, netdev->handshake->aa, ETH_ALEN);
memcpy(netdev->prev_snonce, netdev->handshake->snonce, 32);
handshake_state_new_snonce(netdev->handshake);
netdev->prev_frequency = netdev->frequency;
netdev->frequency = target_bss->frequency;
handshake_state_set_authenticator_address(netdev->handshake,
target_bss->addr);
fast_transition: fix crash by parsing RSN IE only if present When performing a fast transition to another OPEN network the RSN element won't be there and therefore the bss->rsne is gonna be NULL. Fix crash by not accessing the rsne member when performing a fast transition to an AP that doe snot advertise any RSN IE. Crash caught with gdb: src/station.c:station_transition_start() 186, target 34:8f:27:2f:b8:fc Program received signal SIGSEGV, Segmentation fault. handshake_state_set_authenticator_ie (s=0x555555626eb0, ie=0x0) at src/handshake.c:163 163 s->authenticator_ie = l_memdup(ie, ie[1] + 2u); (gdb) bt #0 handshake_state_set_authenticator_ie (s=0x555555626eb0, ie=0x0) at src/handshake.c:163 #1 0x0000555555561a98 in fast_transition (netdev=0x55555562fbe0, target_bss=0x55555561f4a0, over_air=over_air@entry=true, cb=0x55555556d5b0 <station_fast_transition_cb>) at src/netdev.c:3164 #2 0x0000555555565dfd in netdev_fast_transition (netdev=<optimized out>, target_bss=<optimized out>, cb=<optimized out>) at src/netdev.c:3232 #3 0x000055555556ccbd in station_transition_start (bss=0x55555561f4a0, station=0x555555617da0) at src/station.c:1261 #4 station_roam_scan_notify (err=<optimized out>, bss_list=<optimized out>, userdata=0x555555617da0) at src/station.c:1444 #5 0x0000555555579560 in scan_finished (sc=0x55555562bf80, err=err@entry=0, bss_list=0x55555561bd90, sr=0x555555626b30, wiphy=<optimized out>) at src/scan.c:1234 #6 0x0000555555579620 in get_scan_done (user=0x555555618920) at src/scan.c:1264 #7 0x00005555555abd23 in destroy_request (data=0x55555561b000) at ell/genl.c:673 #8 0x00005555555ac129 in process_unicast (nlmsg=0x7fffffffc310, genl=0x55555560b7a0) at ell/genl.c:940 #9 received_data (io=<optimized out>, user_data=0x55555560b7a0) at ell/genl.c:1039 #10 0x00005555555a8aa3 in io_callback (fd=<optimized out>, events=1, user_data=0x55555560b840) at ell/io.c:126 #11 0x00005555555a7ccd in l_main_iterate (timeout=<optimized out>) at ell/main.c:473 #12 0x00005555555a7d9c in l_main_run () at ell/main.c:520 #13 l_main_run () at ell/main.c:502 #14 0x00005555555a7fac in l_main_run_with_signal (callback=<optimized out>, user_data=0x0) at ell/main.c:642 #15 0x000055555555e5b8 in main (argc=<optimized out>, argv=<optimized out>) at src/main.c:519
2019-08-28 21:06:15 +02:00
if (target_bss->rsne)
handshake_state_set_authenticator_ie(netdev->handshake,
target_bss->rsne);
memcpy(netdev->handshake->mde + 2, target_bss->mde, 3);
netdev->operational = false;
netdev->in_ft = true;
netdev->connect_cb = cb;
/*
* 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.
*/
2019-04-03 18:47:09 +02:00
nhs = l_container_of(netdev->handshake,
2018-06-22 02:32:55 +02:00
struct netdev_handshake_state, super);
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
/* reset key states just as we do in initialization */
nhs->complete = false;
nhs->ptk_installed = false;
nhs->gtk_installed = true;
nhs->igtk_installed = true;
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;
}
2018-06-22 02:32:55 +02:00
if (nhs->group_management_new_key_cmd_id) {
l_genl_family_cancel(nl80211,
2018-06-22 02:32:55 +02:00
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);
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
}
if (over_air)
netdev->ap = ft_over_air_sm_new(netdev->handshake,
netdev_ft_tx_authenticate,
netdev_ft_tx_associate, netdev);
else
netdev->ap = ft_over_ds_sm_new(netdev->handshake,
netdev_ft_over_ds_tx_authenticate,
netdev_ft_tx_associate, netdev);
if (!auth_proto_start(netdev->ap))
goto restore_snonce;
return 0;
restore_snonce:
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
return err;
}
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
return fast_transition(netdev, target_bss, true, cb);
}
int netdev_fast_transition_over_ds(struct netdev *netdev,
struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
return fast_transition(netdev, target_bss, false, cb);
}
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))
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;
}
static void netdev_neighbor_report_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *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)
{
if (l_genl_msg_get_error(msg) < 0)
l_debug("error sending SA Query request");
}
static void netdev_sa_query_req_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *user_data)
{
uint8_t sa_resp[4];
uint16_t transaction;
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)) {
l_error("error sending SA Query response");
return;
}
}
static void netdev_sa_query_resp_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *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;
if (l_genl_msg_get_error(msg) < 0) {
l_debug("error sending SA Query request");
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)) {
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 void netdev_mlme_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);
l_debug("MLME notification %s(%u)", nl80211cmd_to_string(cmd), 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) {
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;
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;
}
}
struct frame_prefix_info {
uint16_t frame_type;
const uint8_t *body;
size_t body_len;
};
static bool netdev_frame_watch_match_prefix(const void *a, const void *b)
{
const struct watchlist_item *item = a;
const struct netdev_frame_watch *fw =
2019-04-03 18:47:09 +02:00
l_container_of(item, struct netdev_frame_watch, super);
const struct frame_prefix_info *info = b;
return fw->frame_type == info->frame_type &&
fw->prefix_len <= info->body_len &&
(fw->prefix_len == 0 ||
!memcmp(fw->prefix, info->body, fw->prefix_len));
}
static void netdev_mgmt_frame_event(struct l_genl_msg *msg,
struct netdev *netdev)
{
struct l_genl_attr attr;
uint16_t type, len, frame_len;
const void *data;
const struct mmpdu_header *mpdu = NULL;
const uint8_t *body;
struct frame_prefix_info info;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_FRAME:
if (mpdu)
return;
mpdu = mpdu_validate(data, len);
if (!mpdu)
l_error("Frame didn't validate as MMPDU");
frame_len = len;
break;
}
}
if (!mpdu)
return;
body = mmpdu_body(mpdu);
if (memcmp(mpdu->address_1, netdev->addr, 6) &&
2017-09-22 05:06:32 +02:00
!util_is_broadcast_address(mpdu->address_1))
return;
/* Only match the frame type and subtype like the kernel does */
#define FC_FTYPE_STYPE_MASK 0x00fc
info.frame_type = l_get_le16(mpdu) & FC_FTYPE_STYPE_MASK;
info.body = (const uint8_t *) body;
info.body_len = (const uint8_t *) mpdu + frame_len - body;
WATCHLIST_NOTIFY_MATCHES(&netdev->frame_watches,
netdev_frame_watch_match_prefix, &info,
netdev_frame_watch_func_t,
netdev, mpdu, body, info.body_len);
}
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;
err = l_genl_msg_get_error(msg);
l_debug("%d", err);
if (err < 0)
l_info("CMD_CONTROL_PORT failed: %s", strerror(-err));
}
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) {
case NL80211_CMD_FRAME:
netdev_mgmt_frame_event(msg, netdev);
break;
2018-03-13 21:27:20 +01:00
case NL80211_CMD_CONTROL_PORT_FRAME:
netdev_control_port_frame_event(msg, netdev);
break;
}
}
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];
if (levels_num == 0) {
thold_list[0] = LOW_SIGNAL_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 >= LOW_SIGNAL_THRESHOLD && !low_sig_added) {
thold_list[thold_count++] =
LOW_SIGNAL_THRESHOLD;
low_sig_added = true;
/* Duplicate values are not allowed */
if (val == LOW_SIGNAL_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)
{
2019-08-04 08:16:56 +02:00
int r = l_genl_msg_get_error(msg);
if (!r)
return;
l_error("CMD_SET_CQM failed: %d(%s)", r, strerror(-r));
}
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 int netdev_cqm_rssi_update(struct netdev *netdev)
{
struct l_genl_msg *msg;
2019-08-04 08:16:56 +02:00
l_debug("");
if (!wiphy_has_ext_feature(netdev->wiphy,
NL80211_EXT_FEATURE_CQM_RSSI_LIST))
return 0;
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 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;
netdev->type = req->pending_type;
/* Set RSSI threshold for CQM notifications */
if (netdev->type == NL80211_IFTYPE_STATION)
netdev_cqm_rssi_update(netdev);
/* If the netdev was down originally, we're done */
if (!req->bring_up)
goto done;
netdev->set_powered_cmd_id =
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 =
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 (old_up != new_up)
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;
}
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 void netdev_set_mac_cb(int error, uint16_t type, const void *data,
uint32_t len, void *user_data)
{
struct netdev *netdev = user_data;
if (error)
l_error("Error setting mac address on %d: %s", netdev->index,
strerror(-error));
netdev->set_powered_cmd_id =
rtnl_set_powered(rtnl, netdev->index, true,
netdev_initial_up_cb, netdev, NULL);
}
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->mac_randomize_once) {
uint8_t addr[ETH_ALEN];
wiphy_generate_random_address(netdev->wiphy, addr);
l_debug("Setting initial random address on "
"ifindex: %d to: "MAC, netdev->index, MAC_STR(addr));
netdev->set_powered_cmd_id =
rtnl_set_mac(rtnl, netdev->index, addr,
netdev_set_mac_cb, netdev, NULL);
return;
}
netdev->set_powered_cmd_id =
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->mac_randomize_once) {
uint8_t addr[ETH_ALEN];
wiphy_generate_random_address(netdev->wiphy, addr);
l_debug("Setting initial random address on "
"ifindex: %d to: "MAC, netdev->index, MAC_STR(addr));
netdev->set_powered_cmd_id =
rtnl_set_mac(rtnl, netdev->index, addr,
netdev_set_mac_cb, netdev, NULL);
return;
}
cb = powered ? netdev_initial_down_cb : netdev_initial_up_cb;
netdev->set_powered_cmd_id =
rtnl_set_powered(rtnl, ifi->ifi_index, !powered, cb, netdev,
NULL);
2016-06-20 12:42:03 +02:00
}
static void netdev_frame_watch_free(struct watchlist_item *item)
{
struct netdev_frame_watch *fw =
2019-04-03 18:47:09 +02:00
l_container_of(item, struct netdev_frame_watch, super);
l_free(fw->prefix);
l_free(fw);
}
static const struct watchlist_ops netdev_frame_watch_ops = {
.item_free = netdev_frame_watch_free,
};
static void netdev_frame_cb(struct l_genl_msg *msg, void *user_data)
{
if (l_genl_msg_get_error(msg) < 0)
l_error("Could not register frame watch type %04x: %i",
L_PTR_TO_UINT(user_data), l_genl_msg_get_error(msg));
}
uint32_t netdev_frame_watch_add(struct netdev *netdev, uint16_t frame_type,
const uint8_t *prefix, size_t prefix_len,
netdev_frame_watch_func_t handler,
void *user_data)
{
struct netdev_frame_watch *fw;
struct l_genl_msg *msg;
struct frame_prefix_info info = { frame_type, prefix, prefix_len };
bool registered;
uint32_t id;
registered = l_queue_find(netdev->frame_watches.items,
netdev_frame_watch_match_prefix,
&info);
fw = l_new(struct netdev_frame_watch, 1);
fw->frame_type = frame_type;
fw->prefix = prefix_len ? l_memdup(prefix, prefix_len) : NULL;
fw->prefix_len = prefix_len;
id = watchlist_link(&netdev->frame_watches, &fw->super,
handler, user_data, NULL);
if (registered)
return id;
msg = l_genl_msg_new_sized(NL80211_CMD_REGISTER_FRAME, 32 + prefix_len);
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_TYPE, 2, &frame_type);
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH,
prefix_len, prefix);
l_genl_family_send(nl80211, msg, netdev_frame_cb,
L_UINT_TO_PTR(frame_type), NULL);
return id;
}
bool netdev_frame_watch_remove(struct netdev *netdev, uint32_t id)
{
/*
* There's no way to unregister from notifications but that's not a
* problem, we leave them active in the kernel but
* netdev_mgmt_frame_event will ignore these events.
*/
return watchlist_remove(&netdev->frame_watches, id);
}
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, bool random_mac)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const char *ifname = NULL;
uint16_t ifname_len = 0;
const uint8_t *ifaddr = NULL;
const uint32_t *ifindex = NULL, *iftype = NULL;
2019-07-08 16:02:58 +02:00
const uint64_t *wdev = NULL;
struct netdev *netdev;
struct wiphy *wiphy = NULL;
2016-06-20 12:42:03 +02:00
struct ifinfomsg *rtmmsg;
size_t bufsize;
const uint8_t action_neighbor_report_prefix[2] = { 0x05, 0x05 };
const uint8_t action_sa_query_resp_prefix[2] = { 0x08, 0x01 };
const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
const uint8_t action_qos_map_prefix[] = { 0x01, 0x04 };
struct l_io *pae_io = NULL;
if (!l_genl_attr_init(&attr, msg))
return NULL;
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 NULL;
}
ifindex = data;
break;
2019-07-08 16:02:58 +02:00
case NL80211_ATTR_WDEV:
if (len != sizeof(uint64_t)) {
l_warn("Invalid wdev attribute");
return NULL;
}
wdev = data;
break;
case NL80211_ATTR_IFNAME:
if (len > IFNAMSIZ) {
l_warn("Invalid interface name attribute");
return NULL;
}
ifname = data;
ifname_len = len;
break;
case NL80211_ATTR_WIPHY:
if (len != sizeof(uint32_t)) {
l_warn("Invalid wiphy attribute");
return NULL;
}
wiphy = wiphy_find(*((uint32_t *) data));
break;
case NL80211_ATTR_IFTYPE:
if (len != sizeof(uint32_t)) {
l_warn("Invalid interface type attribute");
return NULL;
}
iftype = data;
break;
case NL80211_ATTR_MAC:
if (len != ETH_ALEN) {
l_warn("Invalid interface address attribute");
return NULL;
}
ifaddr = data;
break;
}
}
2016-07-19 23:03:26 +02:00
if (!iftype) {
l_warn("Missing iftype attribute");
return NULL;
2016-07-19 23:03:26 +02:00
}
2019-07-08 16:02:58 +02:00
if (!wiphy || !ifindex || !wdev || !ifaddr || !ifname) {
l_warn("Unable to parse interface information");
return NULL;
}
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) {
pae_io = pae_open(*ifindex);
if (!pae_io) {
l_error("Unable to open PAE interface");
return NULL;
}
}
netdev = l_new(struct netdev, 1);
netdev->index = *ifindex;
2019-07-08 16:02:58 +02:00
netdev->wdev_id = *wdev;
netdev->type = *iftype;
netdev->rekey_offload_support = true;
memcpy(netdev->addr, ifaddr, sizeof(netdev->addr));
memcpy(netdev->name, ifname, ifname_len);
netdev->wiphy = wiphy;
netdev->pae_over_nl80211 = pae_io == NULL;
netdev->mac_randomize_once = random_mac;
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->frame_watches, &netdev_frame_watch_ops);
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;
rtmmsg->ifi_index = *ifindex;
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);
/* Subscribe to Management -> Action -> RM -> Neighbor Report frames */
netdev_frame_watch_add(netdev, 0x00d0, action_neighbor_report_prefix,
sizeof(action_neighbor_report_prefix),
netdev_neighbor_report_frame_event, NULL);
netdev_frame_watch_add(netdev, 0x00d0, action_sa_query_resp_prefix,
sizeof(action_sa_query_resp_prefix),
netdev_sa_query_resp_frame_event, NULL);
netdev_frame_watch_add(netdev, 0x00d0, action_sa_query_req_prefix,
sizeof(action_sa_query_req_prefix),
netdev_sa_query_req_frame_event, NULL);
netdev_frame_watch_add(netdev, 0x00d0, action_ft_response_prefix,
sizeof(action_ft_response_prefix),
netdev_ft_response_frame_event, NULL);
if (wiphy_supports_qos_set_map(netdev->wiphy))
netdev_frame_watch_add(netdev, 0x00d0, action_qos_map_prefix,
sizeof(action_qos_map_prefix),
netdev_qos_map_frame_event, NULL);
/* Set RSSI threshold for CQM notifications */
if (netdev->type == NL80211_IFTYPE_STATION)
netdev_cqm_rssi_update(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();
if (rtnl)
return -EALREADY;
l_debug("Opening route netlink socket");
rtnl = l_netlink_new(NETLINK_ROUTE);
if (!rtnl) {
l_error("Failed to open route netlink socket");
return -EIO;
}
if (getenv("IWD_RTNL_DEBUG"))
l_netlink_set_debug(rtnl, do_debug, "[RTNL] ", NULL);
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_bool(settings, "General", "ControlPortOverNL80211",
&pae_over_nl80211))
pae_over_nl80211 = 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);
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");
return 0;
fail_netlink:
l_netlink_destroy(rtnl);
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_genl_family_free(nl80211);
nl80211 = NULL;
l_debug("Closing route netlink socket");
l_netlink_destroy(rtnl);
rtnl = NULL;
}
void netdev_shutdown(void)
{
if (!rtnl)
return;
l_queue_foreach(netdev_list, netdev_shutdown_one, NULL);
l_queue_destroy(netdev_list, netdev_free);
netdev_list = NULL;
}
IWD_MODULE(netdev, netdev_init, netdev_exit);
IWD_MODULE_DEPENDS(netdev, eapol);