netdev: use wiphy radio work queue for connections

This adds connection/FT attempts to the radio work queue. This
will ensure that connections aren't delayed or done concurrently
with scanning.
This commit is contained in:
James Prestwood 2020-07-15 14:29:17 -07:00 committed by Denis Kenzior
parent f85fcf2f21
commit 4165d9414f
2 changed files with 141 additions and 58 deletions

View File

@ -136,6 +136,9 @@ struct netdev {
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
struct l_genl_msg *connect_cmd;
struct wiphy_radio_work_item work;
bool connected : 1;
bool operational : 1;
bool rekey_offload_support : 1;
@ -485,6 +488,9 @@ static void netdev_preauth_destroy(void *data)
static void netdev_connect_free(struct netdev *netdev)
{
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
@ -530,6 +536,11 @@ static void netdev_connect_free(struct netdev *netdev)
netdev->ignore_connect_event = false;
netdev->expect_connect_failure = false;
if (netdev->connect_cmd) {
l_genl_msg_unref(netdev->connect_cmd);
netdev->connect_cmd = NULL;
}
netdev_rssi_polling_update(netdev);
if (netdev->connect_cmd_id) {
@ -1019,6 +1030,9 @@ static void netdev_connect_ok(struct netdev *netdev)
}
netdev_rssi_polling_update(netdev);
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs,
@ -1487,6 +1501,9 @@ void netdev_handshake_failed(struct handshake_state *hs, uint16_t reason_code)
if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL))
l_error("error sending DEL_STATION");
}
if (netdev->work.id)
wiphy_radio_work_done(netdev->wiphy, netdev->work.id);
}
static void hardware_rekey_cb(struct l_genl_msg *msg, void *data)
@ -2421,32 +2438,48 @@ static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev,
struct rtnl_data {
struct netdev *netdev;
struct l_genl_msg *cmd_connect;
uint8_t addr[ETH_ALEN];
int ref;
};
static int netdev_begin_connection(struct netdev *netdev,
struct l_genl_msg *cmd_connect)
static int netdev_begin_connection(struct netdev *netdev)
{
if (cmd_connect) {
if (netdev->connect_cmd) {
netdev->connect_cmd_id = l_genl_family_send(nl80211,
cmd_connect, netdev_cmd_connect_cb,
netdev, NULL);
netdev->connect_cmd,
netdev_cmd_connect_cb, netdev,
NULL);
if (!netdev->connect_cmd_id) {
l_genl_msg_unref(cmd_connect);
return -EIO;
}
if (!netdev->connect_cmd_id)
goto failed;
netdev->connect_cmd = NULL;
}
/*
* Set the supplicant address now, this may have already been done for
* a non-randomized address connect, but if we are randomizing we need
* to set it again as the address should have now changed.
*/
handshake_state_set_supplicant_address(netdev->handshake, netdev->addr);
/* set connected since the auth protocols cannot do so internally */
if (netdev->ap && auth_proto_start(netdev->ap))
if (netdev->ap) {
if (!auth_proto_start(netdev->ap))
goto failed;
/*
* set connected since the auth protocols cannot do
* so internally
*/
netdev->connected = true;
}
return 0;
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return -EIO;
}
static void netdev_mac_change_failed(struct netdev *netdev,
@ -2461,8 +2494,6 @@ static void netdev_mac_change_failed(struct netdev *netdev,
* mac_change_cmd_id was set.
*/
if (!netdev_get_is_up(netdev)) {
l_genl_msg_unref(req->cmd_connect);
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DOWN);
@ -2474,7 +2505,7 @@ static void netdev_mac_change_failed(struct netdev *netdev,
*/
l_info("Interface still up after failing to change the MAC, "
"continuing with connection");
if (netdev_begin_connection(netdev, req->cmd_connect) < 0)
if (netdev_begin_connection(netdev) < 0)
goto failed;
return;
@ -2517,7 +2548,7 @@ static void netdev_mac_power_up_cb(int error, uint16_t type,
/*
* Pick up where we left off in netdev_connect_commmon.
*/
if (netdev_begin_connection(netdev, req->cmd_connect) < 0) {
if (netdev_begin_connection(netdev) < 0) {
l_error("Failed to connect after changing MAC");
netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
@ -2576,9 +2607,7 @@ static void netdev_mac_power_down_cb(int error, uint16_t type,
* Returns -EALREADY if the requested MAC matched our current MAC
* Returns -EIO if there was an IO error when powering down
*/
static int netdev_start_powered_mac_change(struct netdev *netdev,
struct scan_bss *bss,
struct l_genl_msg *cmd_connect)
static int netdev_start_powered_mac_change(struct netdev *netdev)
{
struct rtnl_data *req;
uint8_t new_addr[6];
@ -2586,8 +2615,8 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
/* No address set in handshake, use per-network MAC generation */
if (util_mem_is_zero(netdev->handshake->spa, ETH_ALEN))
wiphy_generate_address_from_ssid(netdev->wiphy,
(const char *)bss->ssid,
new_addr);
(const char *)netdev->handshake->ssid,
new_addr);
else
memcpy(new_addr, netdev->handshake->spa, ETH_ALEN);
@ -2600,7 +2629,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
req = l_new(struct rtnl_data, 1);
req->netdev = netdev;
/* This message will need to be unreffed upon any error */
req->cmd_connect = cmd_connect;
req->ref++;
memcpy(req->addr, new_addr, sizeof(req->addr));
@ -2609,7 +2637,6 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
req, netdev_mac_destroy);
if (!netdev->mac_change_cmd_id) {
l_genl_msg_unref(req->cmd_connect);
l_free(req);
return -EIO;
@ -2618,6 +2645,35 @@ static int netdev_start_powered_mac_change(struct netdev *netdev,
return 0;
}
static bool netdev_connection_work_ready(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
if (mac_per_ssid) {
int ret = netdev_start_powered_mac_change(netdev);
if (!ret)
return false;
else if (ret != -EALREADY)
goto failed;
}
if (netdev_begin_connection(netdev) < 0)
goto failed;
return false;
failed:
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return true;
}
static const struct wiphy_radio_work_item_ops connect_work_ops = {
.do_work = netdev_connection_work_ready,
};
static int netdev_connect_common(struct netdev *netdev,
struct l_genl_msg *cmd_connect,
struct scan_bss *bss,
@ -2626,6 +2682,7 @@ static int netdev_connect_common(struct netdev *netdev,
netdev_event_func_t event_filter,
netdev_connect_cb_t cb, void *user_data)
{
netdev->connect_cmd = cmd_connect;
netdev->event_filter = event_filter;
netdev->connect_cb = cb;
netdev->user_data = user_data;
@ -2642,14 +2699,9 @@ static int netdev_connect_common(struct netdev *netdev,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0))
handshake_state_set_no_rekey(hs, true);
if (mac_per_ssid) {
int ret = netdev_start_powered_mac_change(netdev, bss,
cmd_connect);
if (ret != -EALREADY)
return ret;
}
return netdev_begin_connection(netdev, cmd_connect);
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&connect_work_ops);
return 0;
}
int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
@ -2667,7 +2719,7 @@ int netdev_connect(struct netdev *netdev, struct scan_bss *bss,
netdev->type != NL80211_IFTYPE_P2P_CLIENT)
return -ENOTSUP;
if (netdev->connected || netdev->connect_cmd_id)
if (netdev->connected || netdev->connect_cmd_id || netdev->work.id)
return -EISCONN;
switch (hs->akm_suite) {
@ -2709,23 +2761,36 @@ int netdev_disconnect(struct netdev *netdev,
netdev_disconnect_cb_t cb, void *user_data)
{
struct l_genl_msg *disconnect;
bool send_disconnect = true;
if (netdev->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) {
/*
* Three possibilities here:
* 1. We do not actually have a connect in progress (work.id
* is zero), then we can bail out early with an error.
* 2. We have sent CMD_CONNECT but not fully connected. The
* CMD_CONNECT needs to be canceled and a disconnect should
* be sent.
* 3. Queued up the connect work, but haven't sent CMD_CONNECT
* to the kernel. This case we do not need to send a
* disconnect.
*/
if (!netdev->work.id)
return -ENOTCONN;
if (netdev->connect_cmd_id) {
l_genl_family_cancel(nl80211, netdev->connect_cmd_id);
netdev->connect_cmd_id = 0;
}
} else
send_disconnect = false;
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_REASON_CODE_UNSPECIFIED);
@ -2733,19 +2798,23 @@ int netdev_disconnect(struct netdev *netdev,
netdev_connect_free(netdev);
}
disconnect = netdev_build_cmd_disconnect(netdev,
if (send_disconnect) {
disconnect = netdev_build_cmd_disconnect(netdev,
MMPDU_REASON_CODE_DEAUTH_LEAVING);
netdev->disconnect_cmd_id = l_genl_family_send(nl80211, disconnect,
netdev_cmd_disconnect_cb, netdev, NULL);
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;
}
if (!netdev->disconnect_cmd_id) {
l_genl_msg_unref(disconnect);
return -EIO;
}
netdev->disconnect_cb = cb;
netdev->user_data = user_data;
netdev->aborting = true;
netdev->disconnect_cb = cb;
netdev->user_data = user_data;
netdev->aborting = true;
} else if (cb)
cb(netdev, true, user_data);
return 0;
}
@ -3087,12 +3156,31 @@ static void netdev_ft_over_ds_tx_authenticate(struct iovec *iov,
netdev_ft_request_cb);
}
static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
if (!auth_proto_start(netdev->ap)) {
/* Restore original nonce */
memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32);
netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED,
MMPDU_STATUS_CODE_UNSPECIFIED);
return true;
}
return false;
}
static const struct wiphy_radio_work_item_ops ft_work_ops = {
.do_work = netdev_ft_work_ready,
};
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
bool over_air,
netdev_connect_cb_t cb)
{
struct netdev_handshake_state *nhs;
int err = -EINVAL;
if (!netdev->operational)
return -ENOTCONN;
@ -3173,15 +3261,10 @@ static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_ft_over_ds_tx_authenticate,
netdev_ft_tx_associate, netdev);
if (!auth_proto_start(netdev->ap))
goto restore_snonce;
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&ft_work_ops);
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,

View File

@ -2702,10 +2702,6 @@ int station_disconnect(struct station *station)
if (!station->connected_bss)
return -ENOTCONN;
if (netdev_disconnect(station->netdev,
station_disconnect_cb, station) < 0)
return -EIO;
if (station->netconfig)
netconfig_reset(station->netconfig);
@ -2718,6 +2714,10 @@ int station_disconnect(struct station *station)
station_enter_state(station, STATION_STATE_DISCONNECTING);
if (netdev_disconnect(station->netdev,
station_disconnect_cb, station) < 0)
return -EIO;
return 0;
}