/* * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ell/useful.h" #include "linux/nl80211.h" #include "src/iwd.h" #include "src/module.h" #include "src/wiphy.h" #include "src/ie.h" #include "src/mpdu.h" #include "src/eapol.h" #include "src/handshake.h" #include "src/crypto.h" #include "src/scan.h" #include "src/netdev.h" #include "src/ft.h" #include "src/util.h" #include "src/watchlist.h" #include "src/sae.h" #include "src/nl80211util.h" #include "src/nl80211cmd.h" #include "src/owe.h" #include "src/fils.h" #include "src/auth-proto.h" #include "src/frame-xchg.h" #include "src/diagnostic.h" #ifndef ENOTSUPP #define ENOTSUPP 524 #endif enum connection_type { CONNECTION_TYPE_SOFTMAC, CONNECTION_TYPE_FULLMAC, CONNECTION_TYPE_SAE_OFFLOAD, CONNECTION_TYPE_PSK_OFFLOAD, CONNECTION_TYPE_8021X_OFFLOAD, }; static uint32_t unicast_watch; struct netdev_handshake_state { struct handshake_state super; uint32_t pairwise_new_key_cmd_id; uint32_t group_new_key_cmd_id; uint32_t group_management_new_key_cmd_id; uint32_t set_station_cmd_id; uint32_t set_pmk_cmd_id; bool ptk_installed; bool gtk_installed; bool igtk_installed; bool complete; struct netdev *netdev; enum connection_type type; }; struct netdev_ft_over_ds_info { struct ft_ds_info super; struct netdev *netdev; bool parsed : 1; }; struct netdev { uint32_t index; uint64_t wdev_id; char name[IFNAMSIZ]; uint32_t type; uint8_t addr[ETH_ALEN]; struct wiphy *wiphy; unsigned int ifi_flags; uint32_t frequency; netdev_event_func_t event_filter; netdev_connect_cb_t connect_cb; netdev_disconnect_cb_t disconnect_cb; netdev_neighbor_report_cb_t neighbor_report_cb; netdev_command_cb_t adhoc_cb; void *user_data; struct eapol_sm *sm; struct auth_proto *ap; struct handshake_state *handshake; uint32_t connect_cmd_id; uint32_t disconnect_cmd_id; uint32_t join_adhoc_cmd_id; uint32_t leave_adhoc_cmd_id; uint32_t set_interface_cmd_id; uint32_t rekey_offload_cmd_id; uint32_t qos_map_cmd_id; uint32_t mac_change_cmd_id; enum netdev_result result; uint16_t last_code; /* reason or status, depending on result */ struct l_timeout *neighbor_report_timeout; struct l_timeout *sa_query_timeout; struct l_timeout *group_handshake_timeout; uint16_t sa_query_id; uint8_t prev_snonce[32]; int8_t rssi_levels[16]; uint8_t rssi_levels_num; uint8_t cur_rssi_level_idx; int8_t cur_rssi; struct l_timeout *rssi_poll_timeout; uint32_t rssi_poll_cmd_id; uint8_t set_mac_once[6]; struct scan_bss *fw_roam_bss; uint32_t set_powered_cmd_id; netdev_command_cb_t set_powered_cb; void *set_powered_user_data; netdev_destroy_func_t set_powered_destroy; uint32_t get_station_cmd_id; netdev_get_station_cb_t get_station_cb; void *get_station_data; netdev_destroy_func_t get_station_destroy; struct l_idle *disconnect_idle; struct watchlist station_watches; struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */ struct l_genl_msg *connect_cmd; struct l_genl_msg *auth_cmd; struct wiphy_radio_work_item work; struct l_queue *ft_ds_list; bool connected : 1; bool associated : 1; bool operational : 1; bool rekey_offload_support : 1; bool pae_over_nl80211 : 1; bool in_ft : 1; bool cur_rssi_low : 1; bool use_4addr : 1; bool ignore_connect_event : 1; bool expect_connect_failure : 1; bool aborting : 1; bool events_ready : 1; bool retry_auth : 1; }; struct netdev_preauth_state { netdev_preauthenticate_cb_t cb; void *user_data; struct netdev *netdev; }; struct netdev_watch { uint32_t id; netdev_watch_func_t callback; void *user_data; }; static struct l_netlink *rtnl = NULL; static struct l_genl_family *nl80211; static struct l_queue *netdev_list; static struct watchlist netdev_watches; static bool pae_over_nl80211; static bool mac_per_ssid; static unsigned int iov_ie_append(struct iovec *iov, unsigned int n_iov, unsigned int c, const uint8_t *ie) { if (L_WARN_ON(c >= n_iov)) return n_iov; if (!ie) return c; iov[c].iov_base = (void *) ie; iov[c].iov_len = ie[1] + 2; return c + 1u; } const char *netdev_iftype_to_string(uint32_t iftype) { switch (iftype) { case NL80211_IFTYPE_ADHOC: return "ad-hoc"; case NL80211_IFTYPE_STATION: return "station"; case NL80211_IFTYPE_AP: return "ap"; case NL80211_IFTYPE_P2P_CLIENT: return "p2p-client"; case NL80211_IFTYPE_P2P_GO: return "p2p-go"; case NL80211_IFTYPE_P2P_DEVICE: return "p2p-device"; default: break; } return NULL; } static inline bool is_offload(struct handshake_state *hs) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); if (!nhs) return false; switch (nhs->type) { case CONNECTION_TYPE_SOFTMAC: case CONNECTION_TYPE_FULLMAC: /* * 8021x offload does not quite fit into the same category of PSK * offload. First the netdev_connect_event comes prior to EAP meaning * the handshake is not done at this point. In addition it still * requires EAP take place in userspace meaning IWD needs an eapol_sm. * Because of this, and our prior use of 'is_offload', it does not fit * into the same category and will need to be handled specially. */ case CONNECTION_TYPE_8021X_OFFLOAD: return false; case CONNECTION_TYPE_SAE_OFFLOAD: case CONNECTION_TYPE_PSK_OFFLOAD: return true; } return false; } /* Cancels ongoing GTK/IGTK related commands (if any) */ static void netdev_handshake_state_cancel_rekey( struct netdev_handshake_state *nhs) { 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); if (nhs->set_station_cmd_id) { l_genl_family_cancel(nl80211, nhs->set_station_cmd_id); nhs->set_station_cmd_id = 0; } if (nhs->set_pmk_cmd_id) { l_genl_family_cancel(nl80211, nhs->set_pmk_cmd_id); nhs->set_pmk_cmd_id = 0; } } static void netdev_handshake_state_free(struct handshake_state *hs) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); netdev_handshake_state_cancel_all(nhs); 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; /* * Since GTK/IGTK are optional (NO_GROUP_TRAFFIC), we set them as * 'installed' upon initialization. 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; return &nhs->super; } struct wiphy *netdev_get_wiphy(struct netdev *netdev) { return netdev->wiphy; } const uint8_t *netdev_get_address(struct netdev *netdev) { return netdev->addr; } uint32_t netdev_get_ifindex(struct netdev *netdev) { return netdev->index; } uint64_t netdev_get_wdev_id(struct netdev *netdev) { return netdev->wdev_id; } enum netdev_iftype netdev_get_iftype(struct netdev *netdev) { return netdev->type; } const char *netdev_get_name(struct netdev *netdev) { return netdev->name; } bool netdev_get_is_up(struct netdev *netdev) { bool powered = (netdev->ifi_flags & IFF_UP) != 0; /* * If we are in the middle of changing the MAC we are in somewhat of a * no mans land. Technically the iface may be down, but since we are * not emitting any netdev DOWN events we want netdev_get_is_up to * reflect the same state. Once MAC changing finishes any pending * DOWN events will be emitted. */ if (netdev->mac_change_cmd_id && !powered) return true; return powered; } struct handshake_state *netdev_get_handshake(struct netdev *netdev) { return netdev->handshake; } 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'; return path; } 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; if (netdev->set_powered_cb) netdev->set_powered_cb(netdev, error, netdev->set_powered_user_data); netdev->set_powered_cb = NULL; } static void netdev_set_powered_destroy(void *user_data) { struct netdev *netdev = user_data; netdev->set_powered_cmd_id = 0; if (netdev->set_powered_destroy) netdev->set_powered_destroy(netdev->set_powered_user_data); netdev->set_powered_destroy = NULL; netdev->set_powered_user_data = NULL; } 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 = l_rtnl_set_powered(rtnl, netdev->index, powered, netdev_set_powered_result, netdev, netdev_set_powered_destroy); if (!netdev->set_powered_cmd_id) return -EIO; netdev->set_powered_cb = callback; netdev->set_powered_user_data = user_data; netdev->set_powered_destroy = destroy; return 0; } static bool netdev_parse_bitrate(struct l_genl_attr *attr, enum diagnostic_mcs_type *type_out, uint32_t *rate_out, uint8_t *mcs_out) { uint16_t type, len; const void *data; uint32_t rate = 0; uint8_t mcs = 0; enum diagnostic_mcs_type mcs_type = DIAGNOSTIC_MCS_TYPE_NONE; while (l_genl_attr_next(attr, &type, &len, &data)) { switch (type) { case NL80211_RATE_INFO_BITRATE32: if (len != 4) return false; rate = l_get_u32(data); break; case NL80211_RATE_INFO_MCS: if (len != 1) return false; mcs = l_get_u8(data); mcs_type = DIAGNOSTIC_MCS_TYPE_HT; break; case NL80211_RATE_INFO_VHT_MCS: if (len != 1) return false; mcs = l_get_u8(data); mcs_type = DIAGNOSTIC_MCS_TYPE_VHT; break; case NL80211_RATE_INFO_HE_MCS: if (len != 1) return false; mcs = l_get_u8(data); mcs_type = DIAGNOSTIC_MCS_TYPE_HE; break; } } if (!rate) return false; *type_out = mcs_type; *rate_out = rate; if (mcs_type != DIAGNOSTIC_MCS_TYPE_NONE) *mcs_out = mcs; return true; } static bool netdev_parse_sta_info(struct l_genl_attr *attr, struct diagnostic_station_info *info) { uint16_t type, len; const void *data; struct l_genl_attr nested; while (l_genl_attr_next(attr, &type, &len, &data)) { switch (type) { case NL80211_STA_INFO_SIGNAL: if (len != 1) return false; info->cur_rssi = *(const int8_t *) data; info->have_cur_rssi = true; break; case NL80211_STA_INFO_SIGNAL_AVG: if (len != 1) return false; info->avg_rssi = *(const int8_t *) data; info->have_avg_rssi = true; break; case NL80211_STA_INFO_RX_BITRATE: if (!l_genl_attr_recurse(attr, &nested)) return false; if (!netdev_parse_bitrate(&nested, &info->rx_mcs_type, &info->rx_bitrate, &info->rx_mcs)) return false; info->have_rx_bitrate = true; if (info->rx_mcs_type != DIAGNOSTIC_MCS_TYPE_NONE) info->have_rx_mcs = true; break; case NL80211_STA_INFO_TX_BITRATE: if (!l_genl_attr_recurse(attr, &nested)) return false; if (!netdev_parse_bitrate(&nested, &info->tx_mcs_type, &info->tx_bitrate, &info->tx_mcs)) return false; info->have_tx_bitrate = true; if (info->tx_mcs_type != DIAGNOSTIC_MCS_TYPE_NONE) info->have_tx_mcs = true; break; case NL80211_STA_INFO_EXPECTED_THROUGHPUT: if (len != 4) return false; info->expected_throughput = l_get_u32(data); info->have_expected_throughput = true; break; } } return true; } static void netdev_set_rssi_level_idx(struct netdev *netdev) { uint8_t new_level; for (new_level = 0; new_level < netdev->rssi_levels_num; new_level++) if (netdev->cur_rssi >= netdev->rssi_levels[new_level]) break; netdev->cur_rssi_level_idx = new_level; } static void netdev_rssi_poll_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; struct l_genl_attr attr, nested; uint16_t type, len; const void *data; bool found; struct diagnostic_station_info info; uint8_t prev_rssi_level_idx = netdev->cur_rssi_level_idx; netdev->rssi_poll_cmd_id = 0; if (!l_genl_attr_init(&attr, msg)) goto done; found = false; while (l_genl_attr_next(&attr, &type, &len, &data)) { if (type != NL80211_ATTR_STA_INFO) continue; if (!l_genl_attr_recurse(&attr, &nested)) goto done; if (!netdev_parse_sta_info(&nested, &info)) goto done; found = true; break; } if (!found || !info.have_cur_rssi) goto done; netdev->cur_rssi = info.cur_rssi; /* * Note we don't have to handle LOW_SIGNAL_THRESHOLD here. The * CQM single threshold RSSI monitoring should work even if the * kernel driver doesn't support multiple thresholds. So the * polling only handles the client-supplied threshold list. */ netdev_set_rssi_level_idx(netdev); if (netdev->cur_rssi_level_idx != prev_rssi_level_idx) netdev->event_filter(netdev, NETDEV_EVENT_RSSI_LEVEL_NOTIFY, &netdev->cur_rssi_level_idx, netdev->user_data); done: /* Rearm timer */ l_timeout_modify(netdev->rssi_poll_timeout, 6); } static void netdev_rssi_poll(struct l_timeout *timeout, void *user_data) { struct netdev *netdev = user_data; struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, netdev->handshake->aa); netdev->rssi_poll_cmd_id = l_genl_family_send(nl80211, msg, netdev_rssi_poll_cb, netdev, NULL); } /* To be called whenever operational or rssi_levels_num are updated */ static void netdev_rssi_polling_update(struct netdev *netdev) { if (wiphy_has_ext_feature(netdev->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST)) return; if (netdev->operational && netdev->rssi_levels_num > 0) { if (netdev->rssi_poll_timeout) return; netdev->rssi_poll_timeout = l_timeout_create(1, netdev_rssi_poll, netdev, NULL); } else { if (!netdev->rssi_poll_timeout) return; l_timeout_remove(netdev->rssi_poll_timeout); netdev->rssi_poll_timeout = NULL; if (netdev->rssi_poll_cmd_id) { l_genl_family_cancel(nl80211, netdev->rssi_poll_cmd_id); netdev->rssi_poll_cmd_id = 0; } } } static void netdev_preauth_destroy(void *data) { struct netdev_preauth_state *state = data; if (state->cb) state->cb(state->netdev, NETDEV_RESULT_ABORTED, NULL, state->user_data); l_free(state); } static void netdev_ft_ds_entry_free(void *data) { struct netdev_ft_over_ds_info *info = data; ft_ds_info_free(&info->super); } static void netdev_connect_free(struct netdev *netdev) { if (netdev->work.id) wiphy_radio_work_done(netdev->wiphy, netdev->work.id); if (netdev->sm) { eapol_sm_free(netdev->sm); netdev->sm = NULL; } if (netdev->ap) { auth_proto_free(netdev->ap); netdev->ap = NULL; } eapol_preauth_cancel(netdev->index); if (netdev->handshake) { handshake_state_free(netdev->handshake); netdev->handshake = NULL; } if (netdev->neighbor_report_cb) { netdev->neighbor_report_cb(netdev, -ENOTCONN, NULL, 0, netdev->user_data); netdev->neighbor_report_cb = NULL; l_timeout_remove(netdev->neighbor_report_timeout); } if (netdev->sa_query_timeout) { l_timeout_remove(netdev->sa_query_timeout); netdev->sa_query_timeout = NULL; } if (netdev->group_handshake_timeout) { l_timeout_remove(netdev->group_handshake_timeout); netdev->group_handshake_timeout = NULL; } netdev->associated = false; netdev->operational = false; netdev->connected = false; netdev->connect_cb = NULL; netdev->event_filter = NULL; netdev->user_data = NULL; netdev->result = NETDEV_RESULT_OK; netdev->last_code = 0; netdev->in_ft = false; netdev->ignore_connect_event = false; netdev->expect_connect_failure = false; netdev->cur_rssi_low = false; if (netdev->connect_cmd) { l_genl_msg_unref(netdev->connect_cmd); netdev->connect_cmd = NULL; } netdev_rssi_polling_update(netdev); 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; } if (netdev->ft_ds_list) { l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free); netdev->ft_ds_list = NULL; } } static void netdev_connect_failed(struct netdev *netdev, enum netdev_result result, uint16_t status_or_reason) { netdev_connect_cb_t connect_cb = netdev->connect_cb; netdev_event_func_t event_filter = netdev->event_filter; void *connect_data = netdev->user_data; /* Done this way to allow re-entrant netdev_connect calls */ netdev_connect_free(netdev); if (connect_cb) connect_cb(netdev, result, &status_or_reason, connect_data); else if (event_filter) { /* NETDEV_EVENT_DISCONNECT_BY_SME expects a reason code */ if (result != NETDEV_RESULT_HANDSHAKE_FAILED) status_or_reason = MMPDU_REASON_CODE_UNSPECIFIED; event_filter(netdev, NETDEV_EVENT_DISCONNECT_BY_SME, &status_or_reason, connect_data); } } static void netdev_disconnect_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; netdev->disconnect_cmd_id = 0; netdev_connect_failed(netdev, netdev->result, netdev->last_code); } static void netdev_free(void *data) { struct netdev *netdev = data; l_debug("Freeing netdev %s[%d]", netdev->name, netdev->index); netdev->ifi_flags &= ~IFF_UP; if (netdev->events_ready) WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_DEL); if (netdev->neighbor_report_cb) { netdev->neighbor_report_cb(netdev, -ENODEV, NULL, 0, netdev->user_data); netdev->neighbor_report_cb = NULL; l_timeout_remove(netdev->neighbor_report_timeout); } if (netdev->connected || netdev->connect_cmd_id || netdev->work.id) netdev_connect_free(netdev); if (netdev->disconnect_cmd_id) { l_genl_family_cancel(nl80211, netdev->disconnect_cmd_id); netdev->disconnect_cmd_id = 0; if (netdev->disconnect_cb) netdev->disconnect_cb(netdev, true, netdev->user_data); netdev->disconnect_cb = NULL; netdev->user_data = NULL; } if (netdev->disconnect_idle) { l_idle_remove(netdev->disconnect_idle); netdev->disconnect_idle = NULL; } if (netdev->join_adhoc_cmd_id) { l_genl_family_cancel(nl80211, netdev->join_adhoc_cmd_id); netdev->join_adhoc_cmd_id = 0; } if (netdev->leave_adhoc_cmd_id) { l_genl_family_cancel(nl80211, netdev->leave_adhoc_cmd_id); netdev->leave_adhoc_cmd_id = 0; } if (netdev->set_powered_cmd_id) { l_netlink_cancel(rtnl, netdev->set_powered_cmd_id); netdev->set_powered_cmd_id = 0; } if (netdev->rekey_offload_cmd_id) { l_genl_family_cancel(nl80211, netdev->rekey_offload_cmd_id); netdev->rekey_offload_cmd_id = 0; } if (netdev->qos_map_cmd_id) { l_genl_family_cancel(nl80211, netdev->qos_map_cmd_id); netdev->qos_map_cmd_id = 0; } if (netdev->mac_change_cmd_id) { l_netlink_cancel(rtnl, netdev->mac_change_cmd_id); netdev->mac_change_cmd_id = 0; } if (netdev->get_station_cmd_id) { l_genl_family_cancel(nl80211, netdev->get_station_cmd_id); netdev->get_station_cmd_id = 0; } if (netdev->fw_roam_bss) scan_bss_free(netdev->fw_roam_bss); if (netdev->ft_ds_list) { l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free); netdev->ft_ds_list = NULL; } scan_wdev_remove(netdev->wdev_id); watchlist_destroy(&netdev->station_watches); l_io_destroy(netdev->pae_io); l_free(netdev); } static void netdev_shutdown_one(void *data, void *user_data) { struct netdev *netdev = data; if (netdev_get_is_up(netdev)) l_rtnl_set_powered(rtnl, netdev->index, false, NULL, NULL, NULL); } static bool netdev_match(const void *a, const void *b) { const struct netdev *netdev = a; uint32_t ifindex = L_PTR_TO_UINT(b); return (netdev->index == ifindex); } struct netdev *netdev_find(int ifindex) { return l_queue_find(netdev_list, netdev_match, L_UINT_TO_PTR(ifindex)); } /* Threshold RSSI for roaming to trigger, configurable in main.conf */ static int LOW_SIGNAL_THRESHOLD; static int LOW_SIGNAL_THRESHOLD_5GHZ; static void netdev_cqm_event_rssi_threshold(struct netdev *netdev, uint32_t rssi_event) { int event; if (!netdev->operational) return; if (rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW && rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH) return; if (!netdev->event_filter) return; netdev->cur_rssi_low = (rssi_event == NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW); event = netdev->cur_rssi_low ? NETDEV_EVENT_RSSI_THRESHOLD_LOW : NETDEV_EVENT_RSSI_THRESHOLD_HIGH; netdev->event_filter(netdev, event, NULL, netdev->user_data); } static void netdev_rssi_level_init(struct netdev *netdev) { if (netdev->connected && netdev->rssi_levels_num) netdev_set_rssi_level_idx(netdev); } static void netdev_cqm_event_rssi_value(struct netdev *netdev, int rssi_val) { bool new_rssi_low; uint8_t prev_rssi_level_idx = netdev->cur_rssi_level_idx; int threshold = netdev->frequency > 4000 ? LOW_SIGNAL_THRESHOLD_5GHZ : LOW_SIGNAL_THRESHOLD; if (!netdev->connected) return; if (rssi_val > 127) rssi_val = 127; else if (rssi_val < -127) rssi_val = -127; netdev->cur_rssi = rssi_val; if (!netdev->event_filter) return; new_rssi_low = rssi_val < threshold; if (netdev->cur_rssi_low != new_rssi_low) { int event = new_rssi_low ? NETDEV_EVENT_RSSI_THRESHOLD_LOW : NETDEV_EVENT_RSSI_THRESHOLD_HIGH; netdev->cur_rssi_low = new_rssi_low; netdev->event_filter(netdev, event, NULL, netdev->user_data); } if (!netdev->rssi_levels_num) return; netdev_set_rssi_level_idx(netdev); if (netdev->cur_rssi_level_idx != prev_rssi_level_idx) netdev->event_filter(netdev, NETDEV_EVENT_RSSI_LEVEL_NOTIFY, &netdev->cur_rssi_level_idx, netdev->user_data); } static void netdev_cqm_event(struct l_genl_msg *msg, struct netdev *netdev) { struct l_genl_attr attr; struct l_genl_attr nested; uint16_t type, len; const void *data; uint32_t *rssi_event = NULL; int32_t *rssi_val = NULL; if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_CQM: if (!l_genl_attr_recurse(&attr, &nested)) return; while (l_genl_attr_next(&nested, &type, &len, &data)) { switch (type) { case NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT: if (len != 4) continue; rssi_event = (uint32_t *) data; break; case NL80211_ATTR_CQM_RSSI_LEVEL: if (len != 4) continue; rssi_val = (int32_t *) data; break; } } break; } } if (rssi_event) { if (rssi_val) netdev_cqm_event_rssi_value(netdev, *rssi_val); else netdev_cqm_event_rssi_threshold(netdev, *rssi_event); } } static void netdev_rekey_offload_event(struct l_genl_msg *msg, struct netdev *netdev) { struct l_genl_attr attr; struct l_genl_attr nested; uint16_t type, len; const void *data; uint64_t replay_ctr; if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { 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; } replay_ctr = *((uint64_t *) data); __eapol_update_replay_counter(netdev->index, netdev->addr, netdev->handshake->aa, replay_ctr); 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) { struct l_genl_attr attr; uint16_t type, len; const void *data; const struct mmpdu_header *hdr = NULL; uint16_t reason_code; l_debug(""); /* * If we got to the association phase, process the connect event * instead */ if (!netdev->connected || netdev->associated) return; /* * Handle the bizarre case of AP accepting authentication, then * deauthenticating immediately afterwards */ if (L_WARN_ON(!l_genl_attr_init(&attr, msg))) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_FRAME: hdr = mpdu_validate(data, len); break; } } if (L_WARN_ON(!hdr)) return; /* Ignore any locally generated frames */ if (!memcmp(hdr->address_2, netdev->addr, sizeof(netdev->addr))) return; reason_code = l_get_u8(mmpdu_body(hdr)); l_info("deauth event, src="MAC" dest="MAC" bssid="MAC" reason=%u", MAC_STR(hdr->address_2), MAC_STR(hdr->address_1), MAC_STR(hdr->address_3), reason_code); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } static struct l_genl_msg *netdev_build_cmd_deauthenticate(struct netdev *netdev, uint16_t reason_code) { struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_DEAUTHENTICATE, 128); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_REASON_CODE, 2, &reason_code); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, netdev->handshake->aa); return msg; } static struct l_genl_msg *netdev_build_cmd_del_station(struct netdev *netdev, const uint8_t *sta, uint16_t reason_code, bool disassociate) { struct l_genl_msg *msg; uint8_t subtype = disassociate ? MPDU_MANAGEMENT_SUBTYPE_DISASSOCIATION : MPDU_MANAGEMENT_SUBTYPE_DEAUTHENTICATION; msg = l_genl_msg_new_sized(NL80211_CMD_DEL_STATION, 64); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, 6, sta); l_genl_msg_append_attr(msg, NL80211_ATTR_MGMT_SUBTYPE, 1, &subtype); l_genl_msg_append_attr(msg, NL80211_ATTR_REASON_CODE, 2, &reason_code); return msg; } static void netdev_del_sta_cb(struct l_genl_msg *msg, void *user_data) { int err = l_genl_msg_get_error(msg); const char *ext_error; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_error("DEL_STATION failed: %s", ext_error ? ext_error : strerror(-err)); } int netdev_del_station(struct netdev *netdev, const uint8_t *sta, uint16_t reason_code, bool disassociate) { struct l_genl_msg *msg; msg = netdev_build_cmd_del_station(netdev, sta, reason_code, disassociate); if (!l_genl_family_send(nl80211, msg, netdev_del_sta_cb, NULL, NULL)) return -EIO; return 0; } static void netdev_operstate_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { if (!error) return; l_debug("netdev: %u, error: %s", L_PTR_TO_UINT(user_data), strerror(-error)); } static void netdev_connect_ok(struct netdev *netdev) { l_rtnl_set_linkmode_and_operstate(rtnl, netdev->index, IF_LINK_MODE_DORMANT, IF_OPER_UP, netdev_operstate_cb, L_UINT_TO_PTR(netdev->index), NULL); netdev->operational = true; if (netdev->fw_roam_bss) { if (netdev->event_filter) netdev->event_filter(netdev, NETDEV_EVENT_ROAMED, netdev->fw_roam_bss, netdev->user_data); else scan_bss_free(netdev->fw_roam_bss); netdev->fw_roam_bss = NULL; } if (netdev->ft_ds_list) { l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free); netdev->ft_ds_list = NULL; } if (netdev->connect_cb) { netdev->connect_cb(netdev, NETDEV_RESULT_OK, NULL, netdev->user_data); netdev->connect_cb = NULL; } netdev_rssi_polling_update(netdev); if (netdev->work.id) wiphy_radio_work_done(netdev->wiphy, netdev->work.id); } static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs, int err) { 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; } if (netdev->group_handshake_timeout) { l_timeout_remove(netdev->group_handshake_timeout); netdev->group_handshake_timeout = NULL; } switch (netdev->type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: /* * If we failed due to the netdev being brought down, * just abort the connection and do not try to send a * CMD_DISCONNECT */ if (err == -ENETDOWN) { netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } msg = netdev_build_cmd_disconnect(netdev, MMPDU_REASON_CODE_UNSPECIFIED); netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg, netdev_disconnect_cb, netdev, NULL); break; case NL80211_IFTYPE_AP: if (err == -ENETDOWN) return; msg = netdev_build_cmd_del_station(netdev, nhs->super.spa, MMPDU_REASON_CODE_UNSPECIFIED, false); if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) l_error("error sending DEL_STATION"); break; } netdev->result = NETDEV_RESULT_KEY_SETTING_FAILED; handshake_event(&nhs->super, HANDSHAKE_EVENT_SETTING_KEYS_FAILED, &err); } 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); if (nhs->netdev->type == NL80211_IFTYPE_STATION || nhs->netdev->type == NL80211_IFTYPE_P2P_CLIENT) netdev_connect_ok(nhs->netdev); } } static void netdev_set_station_cb(struct l_genl_msg *msg, void *user_data) { struct netdev_handshake_state *nhs = user_data; struct netdev *netdev = nhs->netdev; int err; nhs->set_station_cmd_id = 0; nhs->ptk_installed = true; if (netdev->type == NL80211_IFTYPE_STATION && !netdev->connected) return; err = l_genl_msg_get_error(msg); if (err == -EOPNOTSUPP || err == -ENOTSUPP) goto done; if (err < 0) { const char *ext_error = l_genl_msg_get_extended_error(msg); l_error("Set Station failed for ifindex %d:%s", netdev->index, ext_error ? ext_error : strerror(-err)); netdev_setting_keys_failed(nhs, err); return; } done: try_handshake_complete(nhs); } static void netdev_new_group_key_cb(struct l_genl_msg *msg, void *data) { struct netdev_handshake_state *nhs = data; struct netdev *netdev = nhs->netdev; int err = l_genl_msg_get_error(msg); nhs->group_new_key_cmd_id = 0; if (err < 0) { const char *ext_error = l_genl_msg_get_extended_error(msg); l_error("New Key for Group Key failed for ifindex: %d:%s", netdev->index, ext_error ? ext_error : strerror(-err)); netdev_setting_keys_failed(nhs, err); return; } nhs->gtk_installed = true; try_handshake_complete(nhs); } static void netdev_new_group_management_key_cb(struct l_genl_msg *msg, void *data) { struct netdev_handshake_state *nhs = data; struct netdev *netdev = nhs->netdev; int err = l_genl_msg_get_error(msg); nhs->group_management_new_key_cmd_id = 0; if (err < 0) { const char *ext_error = l_genl_msg_get_extended_error(msg); l_error("New Key for Group Mgmt failed for ifindex: %d:%s", netdev->index, ext_error ? ext_error : strerror(-err)); netdev_setting_keys_failed(nhs, err); return; } nhs->igtk_installed = true; try_handshake_complete(nhs); } static bool netdev_copy_tk(uint8_t *tk_buf, const uint8_t *tk, uint32_t cipher, bool authenticator) { switch (cipher) { case CRYPTO_CIPHER_CCMP: /* * 802.11-2016 12.8.3 Mapping PTK to CCMP keys: * "A STA shall use the temporal key as the CCMP key * for MPDUs between the two communicating STAs." */ memcpy(tk_buf, tk, 16); break; case CRYPTO_CIPHER_TKIP: /* * 802.11-2016 12.8.1 Mapping PTK to TKIP keys: * "A STA shall use bits 0-127 of the temporal key as its * input to the TKIP Phase 1 and Phase 2 mixing functions. * * A STA shall use bits 128-191 of the temporal key as * the michael key for MSDUs from the Authenticator's STA * to the Supplicant's STA. * * A STA shall use bits 192-255 of the temporal key as * the michael key for MSDUs from the Supplicant's STA * to the Authenticator's STA." */ if (authenticator) { memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_ENCR_KEY, tk, 16); memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY, tk + 16, 8); memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, tk + 24, 8); } else { memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_ENCR_KEY, tk, 16); memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, tk + 16, 8); memcpy(tk_buf + NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY, tk + 24, 8); } break; default: l_error("Unexpected cipher: %x", cipher); return false; } return true; } static const uint8_t *netdev_choose_key_address( struct netdev_handshake_state *nhs) { return (nhs->super.authenticator) ? nhs->super.spa : nhs->super.aa; } static void netdev_set_gtk(struct handshake_state *hs, uint16_t key_index, const uint8_t *gtk, uint8_t gtk_len, const uint8_t *rsc, uint8_t rsc_len, uint32_t cipher) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); 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; 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; } 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); nhs->group_new_key_cmd_id = l_genl_family_send(nl80211, msg, netdev_new_group_key_cb, nhs, NULL); if (nhs->group_new_key_cmd_id > 0) return; l_genl_msg_unref(msg); netdev_setting_keys_failed(nhs, -EIO); } static void netdev_set_igtk(struct handshake_state *hs, uint16_t key_index, const uint8_t *igtk, uint8_t igtk_len, const uint8_t *ipn, uint8_t ipn_len, uint32_t cipher) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); uint8_t igtk_buf[16]; struct netdev *netdev = nhs->netdev; struct l_genl_msg *msg; nhs->igtk_installed = false; 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; } 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); return; } if (key_index == 0x0400 || key_index == 0x0500) { l_warn("Received an invalid IGTK key index (%04hx)" " that is likely in" " big endian format. Trying to fix and" " proceed anyway", key_index); key_index = bswap_16(key_index); } msg = nl80211_build_new_key_group(netdev->index, cipher, key_index, igtk_buf, igtk_len, ipn, ipn_len, NULL); nhs->group_management_new_key_cmd_id = l_genl_family_send(nl80211, msg, netdev_new_group_management_key_cb, nhs, NULL); if (nhs->group_management_new_key_cmd_id > 0) return; l_genl_msg_unref(msg); netdev_setting_keys_failed(nhs, -EIO); } static void netdev_new_pairwise_key_cb(struct l_genl_msg *msg, void *data) { 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); nhs->pairwise_new_key_cmd_id = 0; if (err < 0) { const char *ext_error = l_genl_msg_get_extended_error(msg); l_error("New Key for Pairwise Key failed for ifindex: %d:%s", netdev->index, ext_error ? ext_error : strerror(-err)); goto error; } /* * Set the AUTHORIZED flag using a SET_STATION command even if * we're already operational, it will not hurt during re-keying * and is necessary after an FT. */ msg = nl80211_build_set_station_authorized(netdev->index, addr); nhs->set_station_cmd_id = l_genl_family_send(nl80211, msg, netdev_set_station_cb, 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; } 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_connect_ok(nhs->netdev); } static void netdev_set_tk(struct handshake_state *hs, const uint8_t *tk, uint32_t cipher) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); uint8_t tk_buf[32]; struct netdev *netdev = nhs->netdev; struct l_genl_msg *msg; const uint8_t *addr = netdev_choose_key_address(nhs); int err; /* * 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)); nhs->pairwise_new_key_cmd_id = l_genl_family_send(nl80211, msg, netdev_new_pairwise_key_cb, nhs, NULL); if (nhs->pairwise_new_key_cmd_id > 0) return; err = -EIO; l_genl_msg_unref(msg); invalid_key: netdev_setting_keys_failed(nhs, err); } static void netdev_set_pmk_cb(struct l_genl_msg *msg, void *user_data) { struct netdev_handshake_state *nhs = user_data; struct netdev *netdev = nhs->netdev; int err = l_genl_msg_get_error(msg); nhs->set_pmk_cmd_id = 0; if (err < 0) { l_error("Error with SET_PMK/SET_STATION"); netdev_setting_keys_failed(nhs, err); return; } handshake_event(netdev->handshake, HANDSHAKE_EVENT_SETTING_KEYS); netdev_connect_ok(netdev); } static void netdev_set_pmk(struct handshake_state *hs, const uint8_t *pmk, size_t pmk_len) { struct l_genl_msg *msg; struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); struct netdev *netdev = nhs->netdev; /* Only relevent for 8021x offload */ if (nhs->type != CONNECTION_TYPE_8021X_OFFLOAD) return; msg = l_genl_msg_new(NL80211_CMD_SET_PMK); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, 6, netdev->handshake->aa); l_genl_msg_append_attr(msg, NL80211_ATTR_PMK, netdev->handshake->pmk_len, netdev->handshake->pmk); nhs->set_pmk_cmd_id = l_genl_family_send(nl80211, msg, netdev_set_pmk_cb, nhs, NULL); if (!nhs->set_pmk_cmd_id) { l_error("Failed to set SET_PMK"); netdev_setting_keys_failed(nhs, -EIO); return; } } void netdev_handshake_failed(struct handshake_state *hs, uint16_t reason_code) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); struct netdev *netdev = nhs->netdev; struct l_genl_msg *msg; l_error("4-Way handshake failed for ifindex: %d, reason: %u", netdev->index, reason_code); netdev->sm = NULL; netdev->result = NETDEV_RESULT_HANDSHAKE_FAILED; netdev->last_code = reason_code; switch (netdev->type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: msg = netdev_build_cmd_disconnect(netdev, reason_code); netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg, netdev_disconnect_cb, netdev, NULL); break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: msg = netdev_build_cmd_del_station(netdev, nhs->super.spa, reason_code, false); if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) l_error("error sending DEL_STATION"); } if (netdev->work.id) wiphy_radio_work_done(netdev->wiphy, netdev->work.id); } static void hardware_rekey_cb(struct l_genl_msg *msg, void *data) { struct netdev *netdev = data; int err; netdev->rekey_offload_cmd_id = 0; err = l_genl_msg_get_error(msg); if (err < 0) { if (err == -EOPNOTSUPP) { l_error("hardware_rekey not supported"); netdev->rekey_offload_support = false; } /* * TODO: Ignore all other errors for now, until WoWLAN is * supported properly */ } } static struct l_genl_msg *netdev_build_cmd_replay_counter(struct netdev *netdev, const uint8_t *kek, const uint8_t *kck, uint64_t replay_ctr) { struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_SET_REKEY_OFFLOAD, 512); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_enter_nested(msg, NL80211_ATTR_REKEY_DATA); l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_KEK, NL80211_KEK_LEN, kek); l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_KCK, NL80211_KCK_LEN, kck); l_genl_msg_append_attr(msg, NL80211_REKEY_DATA_REPLAY_CTR, NL80211_REPLAY_CTR_LEN, &replay_ctr); l_genl_msg_leave_nested(msg); return msg; } static void netdev_set_rekey_offload(uint32_t ifindex, const uint8_t *kek, const uint8_t *kck, uint64_t replay_counter, void *user_data) { struct netdev *netdev; struct l_genl_msg *msg; netdev = netdev_find(ifindex); if (!netdev) return; if (netdev->type != NL80211_IFTYPE_STATION) return; if (!netdev->rekey_offload_support) return; l_debug("%d", netdev->index); msg = netdev_build_cmd_replay_counter(netdev, kek, kck, replay_counter); netdev->rekey_offload_cmd_id = l_genl_family_send(nl80211, msg, hardware_rekey_cb, netdev, NULL); } static void netdev_qos_map_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; int err = l_genl_msg_get_error(msg); const char *ext_error; netdev->qos_map_cmd_id = 0; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_error("Couuld not set QoS Map in kernel: %s", ext_error ? ext_error : strerror(-err)); } /* * TODO: Fix this in the kernel: * * The QoS Map is really of no use to IWD. The kernel requires it to map QoS * network values properly to what it puts into the IP header. The way we have * to let the kernel know is to receive the IE, then give it right back... * * The kernel/driver/firmware *could* simply obtain this information as the * frame comes in and not require userspace to forward it back... but that's a * battle for another day. */ static void netdev_send_qos_map_set(struct netdev *netdev, const uint8_t *qos_set, size_t qos_len) { struct l_genl_msg *msg; if (!wiphy_supports_qos_set_map(netdev->wiphy)) { l_warn("AP sent QoS Map, but capability was not advertised!"); return; } /* * Since this IE comes in on either a management frame or during * Association response we could have potentially already set this. */ if (netdev->qos_map_cmd_id) return; msg = l_genl_msg_new_sized(NL80211_CMD_SET_QOS_MAP, 128 + qos_len); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_QOS_MAP, qos_len, qos_set); netdev->qos_map_cmd_id = l_genl_family_send(nl80211, msg, netdev_qos_map_cb, netdev, NULL); } static void parse_request_ies(struct netdev *netdev, const uint8_t *ies, size_t ies_len) { struct ie_tlv_iter iter; const void *data; const uint8_t *rsnxe = NULL; /* * The driver may have modified the IEs we passed to CMD_CONNECT * before sending them out, the actual IE sent is reflected in the * ATTR_REQ_IE sequence. These are the values EAPoL will need to use. */ ie_tlv_iter_init(&iter, ies, ies_len); while (ie_tlv_iter_next(&iter)) { data = ie_tlv_iter_get_data(&iter); switch (ie_tlv_iter_get_tag(&iter)) { case IE_TYPE_RSN: handshake_state_set_supplicant_ie(netdev->handshake, data - 2); break; case IE_TYPE_RSNX: if (!rsnxe) rsnxe = data - 2; break; case IE_TYPE_VENDOR_SPECIFIC: if (!is_ie_wpa_ie(data, ie_tlv_iter_get_length(&iter))) break; handshake_state_set_supplicant_ie(netdev->handshake, data - 2); break; case IE_TYPE_MOBILITY_DOMAIN: handshake_state_set_mde(netdev->handshake, data - 2); break; } } /* RSNXE element might be omitted when FTing */ handshake_state_set_supplicant_rsnxe(netdev->handshake, rsnxe); } static void netdev_driver_connected(struct netdev *netdev) { netdev->connected = true; if (netdev->event_filter) netdev->event_filter(netdev, NETDEV_EVENT_ASSOCIATING, NULL, netdev->user_data); /* * We register the eapol state machine here, in case the PAE * socket receives EAPoL packets before the nl80211 socket * receives the connected event. The logical sequence of * events can be reversed (e.g. connect_event, then PAE data) * due to scheduling */ if (netdev->sm) eapol_register(netdev->sm); } static void netdev_connect_event(struct l_genl_msg *msg, struct netdev *netdev) { 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; if (netdev->ignore_connect_event) return; /* Work around mwifiex which sends a Connect Event prior to the Ack */ if (netdev->connect_cmd_id) netdev_driver_connected(netdev); if (!netdev->connected) { l_warn("Unexpected connection related event -- " "is another supplicant running?"); return; } if (!l_genl_attr_init(&attr, msg)) { l_debug("attr init failed"); goto error; } 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; } } if (netdev->expect_connect_failure) { /* * The kernel may think we are connected when we are actually * expecting a failure here, e.g. if Authenticate/Associate had * previously failed. If so we need to deauth to let the kernel * know. */ if (status_code && *status_code == 0) goto deauth; else goto error; } /* AP Rejected the authenticate / associate */ if (!status_code || *status_code != 0) goto error; if (!ies) goto process_resp_ies; parse_request_ies(netdev, ies, ies_len); process_resp_ies: if (resp_ies) { const uint8_t *fte = NULL; const uint8_t *qos_set = NULL; size_t qos_len = 0; struct ie_ft_info ft_info; ie_tlv_iter_init(&iter, resp_ies, resp_ies_len); while (ie_tlv_iter_next(&iter)) { data = ie_tlv_iter_get_data(&iter); switch (ie_tlv_iter_get_tag(&iter)) { case IE_TYPE_FAST_BSS_TRANSITION: fte = data - 2; break; case IE_TYPE_QOS_MAP_SET: qos_set = data; qos_len = ie_tlv_iter_get_length(&iter); break; } } /* FILS handles its own FT key derivation */ if (fte && !(netdev->handshake->akm_suite & (IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256 | IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384))) { uint32_t kck_len = handshake_state_get_kck_len(netdev->handshake); /* * If we are here, then most likely we have a FullMac * hw performing initial mobility association. We need * to set the FTE element or the handshake will fail * The firmware accepted the FTE element, so do not * sanitize the contents and just assume they're okay. */ if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2, kck_len, &ft_info) >= 0) { handshake_state_set_fte(netdev->handshake, fte); handshake_state_set_kh_ids(netdev->handshake, ft_info.r0khid, ft_info.r0khid_len, ft_info.r1khid); } else { l_info("CMD_CONNECT Succeeded, but parsing FTE" " failed. Expect handshake failure"); } } if (qos_set) netdev_send_qos_map_set(netdev, qos_set, qos_len); } if (netdev->sm) { /* * Start processing EAPoL frames now that the state machine * has all the input data even in FT mode. */ if (!eapol_start(netdev->sm)) goto error; return; } /* Allow station to sync the PSK to disk */ if (is_offload(netdev->handshake)) handshake_event(netdev->handshake, HANDSHAKE_EVENT_SETTING_KEYS); netdev_connect_ok(netdev); return; error: netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, (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) { 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; 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; 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->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); } } static void netdev_authenticate_event(struct l_genl_msg *msg, struct netdev *netdev) { struct l_genl_attr attr; uint16_t type, len; const void *data; const uint8_t *frame = NULL; size_t frame_len = 0; int ret; uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED; l_debug(""); if (netdev->aborting) return; if (!netdev->connected) { l_warn("Unexpected connection related event -- " "is another supplicant running?"); return; } /* * During Fast Transition we use the authenticate event to start the * reassociation step because the FTE necessary before we can build * the FT Associate command is included in the attached frame and is * not available in the Authenticate command callback. */ if (!netdev->ap) return; if (!l_genl_attr_init(&attr, msg)) { l_debug("attr init failed"); goto auth_error; } while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_TIMED_OUT: l_warn("authentication timed out"); if (auth_proto_auth_timeout(netdev->ap)) return; goto auth_error; case NL80211_ATTR_FRAME: if (frame) goto auth_error; frame = data; frame_len = len; break; } } if (L_WARN_ON(!frame)) goto auth_error; if (netdev->ap) { const struct mmpdu_header *hdr; const struct mmpdu_authentication *auth; if (L_WARN_ON(!(hdr = mpdu_validate(frame, frame_len)))) goto auth_error; auth = mmpdu_body(hdr); status_code = L_CPU_TO_LE16(auth->status); ret = auth_proto_rx_authenticate(netdev->ap, frame, frame_len); if (ret == 0 || ret == -EAGAIN) return; else if (ret > 0) status_code = (uint16_t)ret; } auth_error: netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, status_code); } static void netdev_associate_event(struct l_genl_msg *msg, struct netdev *netdev) { struct l_genl_attr attr; uint16_t type, len; const void *data; size_t frame_len = 0; const uint8_t *frame = NULL; uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED; int ret; l_debug(""); if (!netdev->connected || netdev->aborting) return; if (!netdev->ap) { netdev->associated = true; return; } if (!l_genl_attr_init(&attr, msg)) { l_debug("attr init failed"); return; } while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_TIMED_OUT: l_warn("association timed out"); if (auth_proto_assoc_timeout(netdev->ap)) return; /* * There will be no connect event when Associate times * out. The failed connection must be explicitly * initiated here. */ netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, status_code); return; case NL80211_ATTR_FRAME: frame = data; frame_len = len; break; } } if (L_WARN_ON(!frame)) goto assoc_failed; if (netdev->ap) { const struct mmpdu_header *hdr; const struct mmpdu_association_response *assoc; if (L_WARN_ON(!(hdr = mpdu_validate(frame, frame_len)))) goto assoc_failed; assoc = mmpdu_body(hdr); status_code = L_CPU_TO_LE16(assoc->status_code); ret = auth_proto_rx_associate(netdev->ap, frame, frame_len); if (ret == 0) { bool fils = !!(netdev->handshake->akm_suite & (IE_RSN_AKM_SUITE_FILS_SHA256 | IE_RSN_AKM_SUITE_FILS_SHA384 | IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384 | IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256)); auth_proto_free(netdev->ap); netdev->ap = NULL; netdev->sm = eapol_sm_new(netdev->handshake); eapol_register(netdev->sm); /* Just in case this was a retry */ netdev->ignore_connect_event = false; /* * If in FT and/or FILS we don't force an initial 4-way * handshake and instead just keep the EAPoL state * machine for the rekeys. */ if (netdev->in_ft || fils) eapol_sm_set_require_handshake(netdev->sm, false); netdev->in_ft = false; netdev->associated = true; return; } else if (ret == -EAGAIN) { /* * Here to support OWE retries. OWE will retry * internally, but a connect event will still be emitted */ netdev->ignore_connect_event = true; return; } else if (ret > 0) status_code = (uint16_t)ret; goto assoc_failed; } return; assoc_failed: netdev->result = NETDEV_RESULT_ASSOCIATION_FAILED; netdev->last_code = status_code; netdev->expect_connect_failure = true; } static void netdev_cmd_connect_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; netdev->connect_cmd_id = 0; if (l_genl_msg_get_error(msg) >= 0) { /* * connected should be false if the connect event hasn't come * in yet. i.e. the CMD_CONNECT ack arrived first (typical). * Mark the connection as 'connected' */ if (!netdev->connected) netdev_driver_connected(netdev); return; } netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } static struct l_genl_msg *netdev_build_cmd_authenticate(struct netdev *netdev, uint32_t auth_type) { struct handshake_state *hs = netdev->handshake; struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_AUTHENTICATE, 512); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &netdev->frequency); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, netdev->handshake->aa); l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, hs->ssid_len, hs->ssid); l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type); return msg; } static void netdev_scan_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; if (l_genl_msg_get_error(msg) < 0) { netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } netdev->retry_auth = true; } static void netdev_auth_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; struct handshake_state *hs = netdev->handshake; int err = l_genl_msg_get_error(msg); struct l_genl_msg *scan_msg; if (!err) { l_genl_msg_unref(netdev->auth_cmd); netdev->auth_cmd = NULL; return; } l_debug("Error during auth: %d", err); if (!netdev->auth_cmd || err != -ENOENT) { netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } /* Kernel can't find the BSS in its cache, scan and retry */ scan_msg = scan_build_trigger_scan_bss(netdev->index, netdev->wiphy, netdev->frequency, hs->ssid, hs->ssid_len); if (!l_genl_family_send(nl80211, scan_msg, netdev_scan_cb, netdev, NULL)) { l_genl_msg_unref(scan_msg); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } } static void netdev_new_scan_results_event(struct l_genl_msg *msg, struct netdev *netdev) { if (!netdev->retry_auth) return; l_debug(""); if (!l_genl_family_send(nl80211, netdev->auth_cmd, netdev_auth_cb, netdev, NULL)) { netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } netdev->auth_cmd = NULL; } static void netdev_assoc_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; if (l_genl_msg_get_error(msg) < 0) { l_error("Error sending CMD_ASSOCIATE"); netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } } static void netdev_sae_tx_authenticate(const uint8_t *body, size_t body_len, void *user_data) { struct netdev *netdev = user_data; struct l_genl_msg *msg; msg = netdev_build_cmd_authenticate(netdev, NL80211_AUTHTYPE_SAE); l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_DATA, body_len, body); if (!l_genl_family_send(nl80211, msg, netdev_auth_cb, netdev, NULL)) { l_genl_msg_unref(msg); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } netdev->auth_cmd = l_genl_msg_ref(msg); } static void netdev_sae_tx_associate(void *user_data) { struct netdev *netdev = user_data; struct handshake_state *hs = netdev->handshake; struct l_genl_msg *msg; struct iovec iov[3]; unsigned int n_iov = L_ARRAY_SIZE(iov); unsigned int n_used = 0; msg = netdev_build_cmd_associate_common(netdev); n_used = iov_ie_append(iov, n_iov, n_used, hs->supplicant_ie); n_used = iov_ie_append(iov, n_iov, n_used, hs->mde); n_used = iov_ie_append(iov, n_iov, n_used, hs->supplicant_rsnxe); l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, n_used); if (!l_genl_family_send(nl80211, msg, netdev_assoc_cb, netdev, NULL)) { l_genl_msg_unref(msg); netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } } static void netdev_owe_tx_authenticate(void *user_data) { struct netdev *netdev = user_data; struct l_genl_msg *msg; msg = netdev_build_cmd_authenticate(netdev, NL80211_AUTHTYPE_OPEN_SYSTEM); if (!l_genl_family_send(nl80211, msg, netdev_auth_cb, netdev, NULL)) { l_genl_msg_unref(msg); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } netdev->auth_cmd = l_genl_msg_ref(msg); } static void netdev_owe_tx_associate(struct iovec *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); l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_DATA, body_len, body); if (!l_genl_family_send(nl80211, msg, netdev_auth_cb, netdev, NULL)) { l_genl_msg_unref(msg); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); return; } netdev->auth_cmd = l_genl_msg_ref(msg); } static void netdev_fils_tx_associate(struct iovec *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); } } static struct l_genl_msg *netdev_build_cmd_connect(struct netdev *netdev, struct scan_bss *bss, struct handshake_state *hs, const uint8_t *prev_bssid, const struct iovec *vendor_ies, size_t num_vendor_ies) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); uint32_t auth_type = IE_AKM_IS_SAE(hs->akm_suite) ? NL80211_AUTHTYPE_SAE : NL80211_AUTHTYPE_OPEN_SYSTEM; 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; 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); l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type); switch (nhs->type) { case CONNECTION_TYPE_SOFTMAC: case CONNECTION_TYPE_FULLMAC: break; case CONNECTION_TYPE_SAE_OFFLOAD: l_genl_msg_append_attr(msg, NL80211_ATTR_SAE_PASSWORD, strlen(hs->passphrase), hs->passphrase); break; case CONNECTION_TYPE_PSK_OFFLOAD: l_genl_msg_append_attr(msg, NL80211_ATTR_PMK, 32, hs->pmk); break; case CONNECTION_TYPE_8021X_OFFLOAD: l_genl_msg_append_attr(msg, NL80211_ATTR_WANT_1X_4WAY_HS, 0, NULL); } if (prev_bssid) l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN, prev_bssid); if (bss->capability & IE_BSS_CAP_PRIVACY) l_genl_msg_append_attr(msg, NL80211_ATTR_PRIVACY, 0, NULL); l_genl_msg_append_attr(msg, NL80211_ATTR_SOCKET_OWNER, 0, NULL); if (is_rsn) { uint32_t nl_cipher; uint32_t nl_akm; uint32_t wpa_version; if (hs->pairwise_cipher == IE_RSN_CIPHER_SUITE_CCMP) nl_cipher = CRYPTO_CIPHER_CCMP; else nl_cipher = CRYPTO_CIPHER_TKIP; l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE, 4, &nl_cipher); if (hs->group_cipher == IE_RSN_CIPHER_SUITE_CCMP) nl_cipher = CRYPTO_CIPHER_CCMP; else nl_cipher = CRYPTO_CIPHER_TKIP; l_genl_msg_append_attr(msg, NL80211_ATTR_CIPHER_SUITE_GROUP, 4, &nl_cipher); if (hs->mfp) { uint32_t use_mfp = NL80211_MFP_REQUIRED; l_genl_msg_append_attr(msg, NL80211_ATTR_USE_MFP, 4, &use_mfp); } nl_akm = ie_rsn_akm_suite_to_nl80211(hs->akm_suite); if (nl_akm) l_genl_msg_append_attr(msg, NL80211_ATTR_AKM_SUITES, 4, &nl_akm); if (IE_AKM_IS_SAE(hs->akm_suite)) wpa_version = NL80211_WPA_VERSION_3; else if (hs->wpa_ie) wpa_version = NL80211_WPA_VERSION_1; else wpa_version = NL80211_WPA_VERSION_2; l_genl_msg_append_attr(msg, NL80211_ATTR_WPA_VERSIONS, 4, &wpa_version); l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT, 0, NULL); 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; } struct rtnl_data { struct netdev *netdev; uint8_t addr[ETH_ALEN]; int ref; }; static int netdev_begin_connection(struct netdev *netdev) { if (netdev->connect_cmd) { netdev->connect_cmd_id = l_genl_family_send(nl80211, netdev->connect_cmd, netdev_cmd_connect_cb, netdev, NULL); if (!netdev->connect_cmd_id) goto failed; netdev->connect_cmd = NULL; } /* * Set the supplicant address now, this may have already been done for * a non-randomized address connect, but if we are randomizing we need * to set it again as the address should have now changed. */ handshake_state_set_supplicant_address(netdev->handshake, netdev->addr); if (netdev->ap) { if (!auth_proto_start(netdev->ap)) goto failed; /* * set connected since the auth protocols cannot do * so internally */ netdev->connected = true; } return 0; failed: netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_STATUS_CODE_UNSPECIFIED); return -EIO; } static void netdev_mac_change_failed(struct netdev *netdev, struct rtnl_data *req, int error) { l_error("Error setting mac address on %d: %s", netdev->index, strerror(-error)); /* * If the interface is down and we failed to up it we need to notify * any watchers since we have been skipping the notification while * mac_change_cmd_id was set. */ if (!netdev_get_is_up(netdev)) { WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_DOWN); goto failed; } else { /* * If the interface is up we can still try and connect. This * is a very rare case and most likely will never happen. */ l_info("Interface still up after failing to change the MAC, " "continuing with connection"); if (netdev_begin_connection(netdev) < 0) goto failed; return; } failed: netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_STATUS_CODE_UNSPECIFIED); } static void netdev_mac_destroy(void *user_data) { struct rtnl_data *req = user_data; req->ref--; /* still pending requests? */ if (req->ref) return; l_free(req); } static void netdev_mac_power_up_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { struct rtnl_data *req = user_data; struct netdev *netdev = req->netdev; netdev->mac_change_cmd_id = 0; if (error) { l_error("Error taking interface %u up for per-network MAC " "generation: %s", netdev->index, strerror(-error)); netdev_mac_change_failed(netdev, req, error); return; } /* * Pick up where we left off in netdev_connect_commmon. */ if (netdev_begin_connection(netdev) < 0) { l_error("Failed to connect after changing MAC"); netdev_connect_failed(netdev, NETDEV_RESULT_ASSOCIATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } } static void netdev_mac_power_down_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { struct rtnl_data *req = user_data; struct netdev *netdev = req->netdev; netdev->mac_change_cmd_id = 0; if (error) { l_error("Error taking interface %u down for per-network MAC " "generation: %s", netdev->index, strerror(-error)); netdev_mac_change_failed(netdev, req, error); return; } l_debug("Setting generated address on ifindex: %d to: "MAC, netdev->index, MAC_STR(req->addr)); netdev->mac_change_cmd_id = l_rtnl_set_mac(rtnl, netdev->index, req->addr, true, netdev_mac_power_up_cb, req, netdev_mac_destroy); if (!netdev->mac_change_cmd_id) { netdev_mac_change_failed(netdev, req, -EIO); return; } req->ref++; } /* * TODO: There are some potential race conditions that are being ignored. There * is nothing that IWD itself can do to solve these, they require kernel * changes: * * 1. A perfectly timed ifdown could be ignored. If an external process * brings down an interface just before calling this function we would only * get a single newlink event since there is no state change doing a second * ifdown (nor an error from the kernel). This newlink event would be ignored * since IWD thinks its from our own doing. This would result in IWD changing * the MAC and bringing the interface back up which would look very strange * and unexpected to someone who just tried to ifdown an interface. * * 2. A perfectly timed ifup could result in a failed connection. If an external * process ifup's just after IWD ifdown's but before changing the MAC this * would cause the MAC change to fail. This failure would result in a failed * connection. * * Returns 0 if a MAC change procedure was started. * Returns -EALREADY if the requested MAC matched our current MAC * Returns -EIO if there was an IO error when powering down */ static int netdev_start_powered_mac_change(struct netdev *netdev) { struct rtnl_data *req; uint8_t new_addr[6]; /* No address set in handshake, use per-network MAC generation */ if (l_memeqzero(netdev->handshake->spa, ETH_ALEN)) wiphy_generate_address_from_ssid(netdev->wiphy, (const char *)netdev->handshake->ssid, new_addr); else memcpy(new_addr, netdev->handshake->spa, ETH_ALEN); /* * MAC has already been changed previously, no need to again */ if (!memcmp(new_addr, netdev->addr, sizeof(new_addr))) return -EALREADY; req = l_new(struct rtnl_data, 1); req->netdev = netdev; /* This message will need to be unreffed upon any error */ req->ref++; memcpy(req->addr, new_addr, sizeof(req->addr)); netdev->mac_change_cmd_id = l_rtnl_set_powered(rtnl, netdev->index, false, netdev_mac_power_down_cb, req, netdev_mac_destroy); if (!netdev->mac_change_cmd_id) { l_free(req); return -EIO; } return 0; } static struct l_genl_msg *netdev_build_cmd_cqm_rssi_update( struct netdev *netdev, const int8_t *levels, size_t levels_num) { struct l_genl_msg *msg; uint32_t hyst = 5; int thold_count; int32_t thold_list[levels_num + 2]; int threshold = netdev->frequency > 4000 ? LOW_SIGNAL_THRESHOLD_5GHZ : LOW_SIGNAL_THRESHOLD; if (levels_num == 0) { thold_list[0] = threshold; thold_count = 1; } else { /* * Build the list of all the threshold values we care about: * - the low/high level threshold, * - the value ranges requested by * netdev_set_rssi_report_levels */ unsigned int i; bool low_sig_added = false; thold_count = 0; for (i = 0; i < levels_num; i++) { int32_t val = levels[levels_num - i - 1]; if (i && thold_list[thold_count - 1] >= val) return NULL; if (val >= threshold && !low_sig_added) { thold_list[thold_count++] = threshold; low_sig_added = true; /* Duplicate values are not allowed */ if (val == threshold) continue; } thold_list[thold_count++] = val; } } msg = l_genl_msg_new_sized(NL80211_CMD_SET_CQM, 32 + thold_count * 4); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_enter_nested(msg, NL80211_ATTR_CQM); l_genl_msg_append_attr(msg, NL80211_ATTR_CQM_RSSI_THOLD, thold_count * 4, thold_list); l_genl_msg_append_attr(msg, NL80211_ATTR_CQM_RSSI_HYST, 4, &hyst); l_genl_msg_leave_nested(msg); return msg; } static void netdev_cmd_set_cqm_cb(struct l_genl_msg *msg, void *user_data) { int err = l_genl_msg_get_error(msg); const char *ext_error; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_error("CMD_SET_CQM failed: %s", ext_error ? ext_error : strerror(-err)); } static int netdev_cqm_rssi_update(struct netdev *netdev) { struct l_genl_msg *msg; l_debug(""); if (!wiphy_has_ext_feature(netdev->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST)) msg = netdev_build_cmd_cqm_rssi_update(netdev, NULL, 0); else msg = netdev_build_cmd_cqm_rssi_update(netdev, netdev->rssi_levels, netdev->rssi_levels_num); if (!msg) return -EINVAL; if (!l_genl_family_send(nl80211, msg, netdev_cmd_set_cqm_cb, NULL, NULL)) { l_genl_msg_unref(msg); return -EIO; } return 0; } static bool netdev_connection_work_ready(struct wiphy_radio_work_item *item) { struct netdev *netdev = l_container_of(item, struct netdev, work); netdev->retry_auth = false; if (mac_per_ssid) { int ret = netdev_start_powered_mac_change(netdev); if (!ret) return false; else if (ret != -EALREADY) goto failed; } if (netdev_begin_connection(netdev) < 0) goto failed; return false; failed: netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_STATUS_CODE_UNSPECIFIED); return true; } static void netdev_connection_work_destroy(struct wiphy_radio_work_item *item) { struct netdev *netdev = l_container_of(item, struct netdev, work); if (netdev->auth_cmd) { l_genl_msg_unref(netdev->auth_cmd); netdev->auth_cmd = NULL; } netdev->retry_auth = false; } static const struct wiphy_radio_work_item_ops connect_work_ops = { .do_work = netdev_connection_work_ready, .destroy = netdev_connection_work_destroy, }; static int netdev_handshake_state_setup_connection_type( struct handshake_state *hs) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); struct wiphy *wiphy = nhs->netdev->wiphy; bool softmac = wiphy_supports_cmds_auth_assoc(wiphy); bool canroam = wiphy_supports_firmware_roam(wiphy); /* * Sanity check that any FT AKMs are set only on softmac or on * devices that support firmware roam */ if (L_WARN_ON(IE_AKM_IS_FT(hs->akm_suite) && !softmac && !canroam)) return -ENOTSUP; switch (hs->akm_suite) { case IE_RSN_AKM_SUITE_PSK: case IE_RSN_AKM_SUITE_FT_USING_PSK: case IE_RSN_AKM_SUITE_PSK_SHA256: if (wiphy_has_ext_feature(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK)) goto psk_offload; if (softmac) goto softmac; goto fullmac; case IE_RSN_AKM_SUITE_8021X: case IE_RSN_AKM_SUITE_FT_OVER_8021X: case IE_RSN_AKM_SUITE_8021X_SHA256: case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256: case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384: case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384: if (wiphy_has_ext_feature(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X)) goto offload_1x; if (softmac) goto softmac; goto fullmac; case IE_RSN_AKM_SUITE_SAE_SHA256: case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256: if (wiphy_has_ext_feature(wiphy, NL80211_EXT_FEATURE_SAE_OFFLOAD)) goto sae_offload; if (softmac && wiphy_has_feature(wiphy, NL80211_FEATURE_SAE)) goto softmac; return -EINVAL; case IE_RSN_AKM_SUITE_OWE: case IE_RSN_AKM_SUITE_FILS_SHA256: case IE_RSN_AKM_SUITE_FILS_SHA384: case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256: case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384: /* FILS and OWE have no offload in any upstream driver */ if (softmac) goto softmac; return -ENOTSUP; case IE_RSN_AKM_SUITE_TDLS: case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256: case IE_RSN_AKM_SUITE_OSEN: return -ENOTSUP; } return -ENOTSUP; softmac: nhs->type = CONNECTION_TYPE_SOFTMAC; return 0; fullmac: nhs->type = CONNECTION_TYPE_FULLMAC; return 0; sae_offload: nhs->type = CONNECTION_TYPE_SAE_OFFLOAD; return 0; psk_offload: nhs->type = CONNECTION_TYPE_PSK_OFFLOAD; return 0; offload_1x: nhs->type = CONNECTION_TYPE_8021X_OFFLOAD; return 0; } static void netdev_connect_common(struct netdev *netdev, struct l_genl_msg *cmd_connect, struct scan_bss *bss, struct handshake_state *hs, struct eapol_sm *sm, 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; netdev->handshake = hs; netdev->sm = sm; netdev->frequency = bss->frequency; netdev->cur_rssi = bss->signal_strength / 100; netdev_rssi_level_init(netdev); netdev_cqm_rssi_update(netdev); handshake_state_set_authenticator_address(hs, bss->addr); if (!wiphy_has_ext_feature(netdev->wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0)) handshake_state_set_no_rekey(hs, true); wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1, &connect_work_ops); } int netdev_connect(struct netdev *netdev, struct scan_bss *bss, struct handshake_state *hs, const struct iovec *vendor_ies, size_t num_vendor_ies, netdev_event_func_t event_filter, netdev_connect_cb_t cb, void *user_data) { struct netdev_handshake_state *nhs = l_container_of(hs, struct netdev_handshake_state, super); struct l_genl_msg *cmd_connect = NULL; struct eapol_sm *sm = NULL; bool is_rsn = hs->supplicant_ie != NULL; if (!(netdev->ifi_flags & IFF_UP)) return -ENETDOWN; if (netdev->type != NL80211_IFTYPE_STATION && netdev->type != NL80211_IFTYPE_P2P_CLIENT) return -ENOTSUP; if (netdev->connected || netdev->connect_cmd_id || netdev->work.id) return -EISCONN; if (!is_rsn) { nhs->type = CONNECTION_TYPE_SOFTMAC; goto build_cmd_connect; } if (netdev_handshake_state_setup_connection_type(hs) < 0) return -ENOTSUP; if (nhs->type != CONNECTION_TYPE_SOFTMAC) goto build_cmd_connect; switch (hs->akm_suite) { case IE_RSN_AKM_SUITE_SAE_SHA256: case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256: netdev->ap = sae_sm_new(hs, netdev_sae_tx_authenticate, netdev_sae_tx_associate, netdev); if (sae_sm_is_h2e(netdev->ap)) { uint8_t own_rsnxe[20]; if (wiphy_get_rsnxe(netdev->wiphy, own_rsnxe, sizeof(own_rsnxe))) { set_bit(own_rsnxe + 2, IE_RSNX_SAE_H2E); handshake_state_set_supplicant_rsnxe(hs, own_rsnxe); } } break; case IE_RSN_AKM_SUITE_OWE: netdev->ap = owe_sm_new(hs, netdev_owe_tx_authenticate, netdev_owe_tx_associate, netdev); break; case IE_RSN_AKM_SUITE_FILS_SHA256: case IE_RSN_AKM_SUITE_FILS_SHA384: case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256: case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384: netdev->ap = fils_sm_new(hs, netdev_fils_tx_authenticate, netdev_fils_tx_associate, netdev); break; default: build_cmd_connect: cmd_connect = netdev_build_cmd_connect(netdev, bss, hs, NULL, vendor_ies, num_vendor_ies); if (!is_offload(hs) && (is_rsn || hs->settings_8021x)) { sm = eapol_sm_new(hs); if (nhs->type == CONNECTION_TYPE_8021X_OFFLOAD) eapol_sm_set_require_handshake(sm, false); } } netdev_connect_common(netdev, cmd_connect, bss, hs, sm, event_filter, cb, user_data); return 0; } static void disconnect_idle(struct l_idle *idle, void *user_data) { struct netdev *netdev = user_data; l_idle_remove(idle); netdev->disconnect_idle = NULL; netdev->disconnect_cb(netdev, true, netdev->user_data); } int netdev_disconnect(struct netdev *netdev, netdev_disconnect_cb_t cb, void *user_data) { struct l_genl_msg *disconnect; bool send_disconnect = true; if (!(netdev->ifi_flags & IFF_UP)) return -ENETDOWN; if (netdev->type != NL80211_IFTYPE_STATION && netdev->type != NL80211_IFTYPE_P2P_CLIENT) return -ENOTSUP; if (netdev->disconnect_cmd_id) return -EINPROGRESS; /* Only perform this if we haven't successfully fully associated yet */ if (!netdev->operational) { /* * Three possibilities here: * 1. We do not actually have a connect in progress (work.id * is zero), then we can bail out early with an error. * 2. We have sent CMD_CONNECT but not fully connected. The * CMD_CONNECT needs to be canceled and a disconnect should * be sent. * 3. Queued up the connect work, but haven't sent CMD_CONNECT * to the kernel. This case we do not need to send a * disconnect. */ if (!netdev->work.id) return -ENOTCONN; if (netdev->connect_cmd_id) { l_genl_family_cancel(nl80211, netdev->connect_cmd_id); netdev->connect_cmd_id = 0; } else if (!wiphy_radio_work_is_running(netdev->wiphy, netdev->work.id)) send_disconnect = false; netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_REASON_CODE_UNSPECIFIED); } else { netdev_connect_free(netdev); } if (send_disconnect) { disconnect = netdev_build_cmd_disconnect(netdev, MMPDU_REASON_CODE_DEAUTH_LEAVING); netdev->disconnect_cmd_id = l_genl_family_send(nl80211, disconnect, netdev_cmd_disconnect_cb, netdev, NULL); if (!netdev->disconnect_cmd_id) { l_genl_msg_unref(disconnect); return -EIO; } netdev->disconnect_cb = cb; netdev->user_data = user_data; netdev->aborting = true; } else if (cb) { netdev->disconnect_cb = cb; netdev->user_data = user_data; netdev->disconnect_idle = l_idle_create(disconnect_idle, netdev, NULL); } 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; struct netdev_handshake_state; struct handshake_state *old_hs; struct eapol_sm *sm = NULL, *old_sm; bool is_rsn = hs->supplicant_ie != NULL; if (netdev_handshake_state_setup_connection_type(hs) < 0) return -ENOTSUP; /* TODO: SoftMac SAE/FILS Re-Associations are not suppored yet */ if (L_WARN_ON(IE_AKM_IS_SAE(hs->akm_suite) || IE_AKM_IS_FILS(hs->akm_suite))) return -ENOTSUP; cmd_connect = netdev_build_cmd_connect(netdev, target_bss, hs, orig_bss->addr, NULL, 0); if (is_rsn) sm = eapol_sm_new(hs); old_sm = netdev->sm; old_hs = netdev->handshake; netdev_connect_common(netdev, cmd_connect, target_bss, hs, sm, event_filter, cb, user_data); netdev->associated = false; netdev->operational = false; netdev->connected = false; netdev_rssi_polling_update(netdev); if (old_sm) eapol_sm_free(old_sm); if (old_hs) handshake_state_free(old_hs); return 0; } static void netdev_join_adhoc_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; netdev->join_adhoc_cmd_id = 0; if (netdev->adhoc_cb) netdev->adhoc_cb(netdev, l_genl_msg_get_error(msg), netdev->user_data); } int netdev_join_adhoc(struct netdev *netdev, const char *ssid, struct iovec *extra_ie, size_t extra_ie_elems, bool control_port, netdev_command_cb_t cb, void *user_data) { struct l_genl_msg *cmd; uint32_t ifindex = netdev->index; uint32_t ch_freq = scan_channel_to_freq(6, SCAN_BAND_2_4_GHZ); uint32_t ch_type = NL80211_CHAN_HT20; if (netdev->type != NL80211_IFTYPE_ADHOC) { l_error("iftype is invalid for adhoc: %u", netdev_get_iftype(netdev)); return -ENOTSUP; } if (netdev->join_adhoc_cmd_id || netdev->leave_adhoc_cmd_id) return -EBUSY; netdev->adhoc_cb = cb; netdev->user_data = user_data; cmd = l_genl_msg_new_sized(NL80211_CMD_JOIN_IBSS, 128); l_genl_msg_append_attr(cmd, NL80211_ATTR_IFINDEX, 4, &ifindex); l_genl_msg_append_attr(cmd, NL80211_ATTR_SSID, strlen(ssid), ssid); l_genl_msg_append_attr(cmd, NL80211_ATTR_WIPHY_FREQ, 4, &ch_freq); l_genl_msg_append_attr(cmd, NL80211_ATTR_WIPHY_CHANNEL_TYPE, 4, &ch_type); l_genl_msg_append_attrv(cmd, NL80211_ATTR_IE, extra_ie, extra_ie_elems); l_genl_msg_append_attr(cmd, NL80211_ATTR_SOCKET_OWNER, 0, NULL); if (control_port) { l_genl_msg_append_attr(cmd, NL80211_ATTR_CONTROL_PORT, 0, NULL); if (netdev->pae_over_nl80211) l_genl_msg_append_attr(cmd, NL80211_ATTR_CONTROL_PORT_OVER_NL80211, 0, NULL); } netdev->join_adhoc_cmd_id = l_genl_family_send(nl80211, cmd, netdev_join_adhoc_cb, netdev, NULL); if (!netdev->join_adhoc_cmd_id) { netdev->adhoc_cb = NULL; netdev->user_data = NULL; return -EIO; } return 0; } static void netdev_leave_adhoc_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; netdev->leave_adhoc_cmd_id = 0; if (netdev->adhoc_cb) netdev->adhoc_cb(netdev, l_genl_msg_get_error(msg), netdev->user_data); netdev->adhoc_cb = NULL; } int netdev_leave_adhoc(struct netdev *netdev, netdev_command_cb_t cb, void *user_data) { struct l_genl_msg *cmd; if (netdev->type != NL80211_IFTYPE_ADHOC) { l_error("iftype is invalid for adhoc: %u", netdev_get_iftype(netdev)); return -ENOTSUP; } if (netdev->join_adhoc_cmd_id || netdev->leave_adhoc_cmd_id) return -EBUSY; netdev->adhoc_cb = cb; netdev->user_data = user_data; cmd = l_genl_msg_new_sized(NL80211_CMD_LEAVE_IBSS, 64); l_genl_msg_append_attr(cmd, NL80211_ATTR_IFINDEX, 4, &netdev->index); netdev->leave_adhoc_cmd_id = l_genl_family_send(nl80211, cmd, netdev_leave_adhoc_cb, netdev, NULL); if (!netdev->leave_adhoc_cmd_id) return -EIO; return 0; } static uint32_t netdev_send_action_framev(struct netdev *netdev, const uint8_t *to, struct iovec *iov, size_t iov_len, uint32_t freq, l_genl_msg_func_t callback, void *user_data) { uint32_t id; struct l_genl_msg *msg = nl80211_build_cmd_frame(netdev->index, netdev->addr, to, freq, iov, iov_len); id = l_genl_family_send(nl80211, msg, callback, user_data, NULL); if (!id) l_genl_msg_unref(msg); return id; } static uint32_t netdev_send_action_frame(struct netdev *netdev, const uint8_t *to, const uint8_t *body, size_t body_len, uint32_t freq, l_genl_msg_func_t callback, void *user_data) { struct iovec iov[1]; iov[0].iov_base = (void *)body; iov[0].iov_len = body_len; return netdev_send_action_framev(netdev, to, iov, 1, freq, callback, user_data); } static void netdev_cmd_authenticate_ft_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; netdev->connect_cmd_id = 0; if (l_genl_msg_get_error(msg) < 0) netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } static void netdev_ft_tx_authenticate(struct iovec *iov, size_t iov_len, void *user_data) { struct netdev *netdev = user_data; struct l_genl_msg *cmd_authenticate; cmd_authenticate = netdev_build_cmd_authenticate(netdev, NL80211_AUTHTYPE_FT); l_genl_msg_append_attrv(cmd_authenticate, NL80211_ATTR_IE, iov, iov_len); netdev->connect_cmd_id = l_genl_family_send(nl80211, cmd_authenticate, netdev_cmd_authenticate_ft_cb, netdev, NULL); if (!netdev->connect_cmd_id) { l_genl_msg_unref(cmd_authenticate); goto restore_snonce; } return; restore_snonce: memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32); netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED, MMPDU_STATUS_CODE_UNSPECIFIED); } static int netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len, void *user_data) { struct netdev *netdev = user_data; struct auth_proto *ap = netdev->ap; struct l_genl_msg *msg; msg = netdev_build_cmd_associate_common(netdev); l_genl_msg_append_attr(msg, NL80211_ATTR_PREV_BSSID, ETH_ALEN, ap->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); return -EIO; } return 0; } static void prepare_ft(struct netdev *netdev, const struct scan_bss *target_bss) { struct netdev_handshake_state *nhs; /* * We reuse the handshake_state object and reset what's needed. * Could also create a new object and copy most of the state but * we would end up doing more work. */ memcpy(netdev->prev_snonce, netdev->handshake->snonce, 32); netdev->frequency = target_bss->frequency; handshake_state_set_authenticator_address(netdev->handshake, target_bss->addr); if (target_bss->rsne) handshake_state_set_authenticator_ie(netdev->handshake, target_bss->rsne); memcpy(netdev->handshake->mde + 2, target_bss->mde, 3); netdev->associated = false; netdev->operational = false; netdev->in_ft = true; /* * Cancel commands that could be running because of EAPoL activity * like re-keying, this way the callbacks for those commands don't * have to check if failures resulted from the transition. */ nhs = l_container_of(netdev->handshake, struct netdev_handshake_state, super); /* reset key states just as we do in initialization */ nhs->complete = false; nhs->ptk_installed = false; nhs->gtk_installed = true; nhs->igtk_installed = true; if (nhs->group_new_key_cmd_id) { l_genl_family_cancel(nl80211, nhs->group_new_key_cmd_id); nhs->group_new_key_cmd_id = 0; } if (nhs->group_management_new_key_cmd_id) { l_genl_family_cancel(nl80211, nhs->group_management_new_key_cmd_id); nhs->group_management_new_key_cmd_id = 0; } if (netdev->rekey_offload_cmd_id) { l_genl_family_cancel(nl80211, netdev->rekey_offload_cmd_id); netdev->rekey_offload_cmd_id = 0; } netdev_rssi_polling_update(netdev); netdev_cqm_rssi_update(netdev); if (netdev->sm) { eapol_sm_free(netdev->sm); netdev->sm = NULL; } } static void netdev_ft_over_ds_auth_failed(struct netdev_ft_over_ds_info *info, uint16_t status) { l_queue_remove(info->netdev->ft_ds_list, info); ft_ds_info_free(&info->super); } struct ft_ds_finder { const uint8_t *spa; const uint8_t *aa; }; static bool match_ft_ds_info(const void *a, const void *b) { const struct netdev_ft_over_ds_info *info = a; const struct ft_ds_finder *finder = b; if (memcmp(info->super.spa, finder->spa, 6)) return false; if (memcmp(info->super.aa, finder->aa, 6)) return false; return true; } static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr, const void *body, size_t body_len, int rssi, void *user_data) { struct netdev *netdev = user_data; struct netdev_ft_over_ds_info *info; int ret; uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED; const uint8_t *aa; const uint8_t *spa; const uint8_t *ies; size_t ies_len; struct ft_ds_finder finder; ret = ft_over_ds_parse_action_response(body, body_len, &spa, &aa, &ies, &ies_len); if (ret < 0) return; finder.spa = spa; finder.aa = aa; info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder); if (!info) return; /* Lookup successful, now check the status code */ if (ret > 0) { status_code = (uint16_t)ret; goto ft_error; } ret = ft_over_ds_parse_action_ies(&info->super, netdev->handshake, ies, ies_len); if (ret < 0) goto ft_error; info->parsed = true; return; ft_error: l_debug("FT-over-DS to "MAC" failed (%d)", MAC_STR(info->super.aa), status_code); netdev_ft_over_ds_auth_failed(info, status_code); } static void netdev_qos_map_frame_event(const struct mmpdu_header *hdr, const void *body, size_t body_len, int rssi, void *user_data) { struct netdev *netdev = user_data; /* No point telling the kernel */ if (!netdev->connected) return; if (memcmp(netdev->handshake->aa, hdr->address_2, ETH_ALEN)) return; if (body_len < 5) return; if (l_get_u8(body + 2) != IE_TYPE_QOS_MAP_SET) return; netdev_send_qos_map_set(netdev, body + 4, body_len - 4); } static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item) { struct netdev *netdev = l_container_of(item, struct netdev, work); if (!auth_proto_start(netdev->ap)) { /* Restore original nonce */ memcpy(netdev->handshake->snonce, netdev->prev_snonce, 32); netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_STATUS_CODE_UNSPECIFIED); return true; } return false; } static const struct wiphy_radio_work_item_ops ft_work_ops = { .do_work = netdev_ft_work_ready, }; int netdev_fast_transition(struct netdev *netdev, const struct scan_bss *target_bss, const struct scan_bss *orig_bss, netdev_connect_cb_t cb) { if (!netdev->operational) return -ENOTCONN; if (!netdev->handshake->mde || !target_bss->mde_present || l_get_le16(netdev->handshake->mde + 2) != l_get_le16(target_bss->mde)) return -EINVAL; prepare_ft(netdev, target_bss); handshake_state_new_snonce(netdev->handshake); netdev->connect_cb = cb; netdev->ap = ft_over_air_sm_new(netdev->handshake, netdev_ft_tx_authenticate, netdev_ft_tx_associate, netdev); memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN); wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1, &ft_work_ops); return 0; } int netdev_fast_transition_over_ds(struct netdev *netdev, const struct scan_bss *target_bss, const struct scan_bss *orig_bss, netdev_connect_cb_t cb) { struct netdev_ft_over_ds_info *info; struct ft_ds_finder finder; if (!netdev->operational) return -ENOTCONN; if (!netdev->handshake->mde || !target_bss->mde_present || l_get_le16(netdev->handshake->mde + 2) != l_get_le16(target_bss->mde)) return -EINVAL; finder.spa = netdev->addr; finder.aa = target_bss->addr; info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder); if (!info || !info->parsed) return -ENOENT; prepare_ft(netdev, target_bss); ft_over_ds_prepare_handshake(&info->super, netdev->handshake); netdev->connect_cb = cb; netdev->ap = ft_over_ds_sm_new(netdev->handshake, netdev_ft_tx_associate, netdev); memcpy(netdev->ap->prev_bssid, orig_bss->addr, ETH_ALEN); wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1, &ft_work_ops); return 0; } static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data) { struct netdev_ft_over_ds_info *info = user_data; if (l_genl_msg_get_error(msg) < 0) { l_error("Could not send CMD_FRAME for FT-over-DS"); netdev_ft_over_ds_auth_failed(info, MMPDU_STATUS_CODE_UNSPECIFIED); } } static void netdev_ft_ds_info_free(struct ft_ds_info *ft) { struct netdev_ft_over_ds_info *info = l_container_of(ft, struct netdev_ft_over_ds_info, super); l_free(info); } int netdev_fast_transition_over_ds_action(struct netdev *netdev, const struct scan_bss *target_bss) { struct netdev_ft_over_ds_info *info; uint8_t ft_req[14]; struct handshake_state *hs = netdev->handshake; struct iovec iovs[5]; uint8_t buf[512]; size_t len; if (!netdev->operational) return -ENOTCONN; if (!netdev->handshake->mde || !target_bss->mde_present || l_get_le16(netdev->handshake->mde + 2) != l_get_le16(target_bss->mde)) return -EINVAL; l_debug(""); info = l_new(struct netdev_ft_over_ds_info, 1); info->netdev = netdev; memcpy(info->super.spa, hs->spa, ETH_ALEN); memcpy(info->super.aa, target_bss->addr, ETH_ALEN); memcpy(info->super.mde, target_bss->mde, sizeof(info->super.mde)); l_getrandom(info->super.snonce, 32); info->super.free = netdev_ft_ds_info_free; ft_req[0] = 6; /* FT category */ ft_req[1] = 1; /* FT Request action */ memcpy(ft_req + 2, netdev->addr, 6); memcpy(ft_req + 8, info->super.aa, 6); iovs[0].iov_base = ft_req; iovs[0].iov_len = sizeof(ft_req); if (!ft_build_authenticate_ies(hs, info->super.snonce, buf, &len)) goto failed; iovs[1].iov_base = buf; iovs[1].iov_len = len; iovs[2].iov_base = NULL; if (!netdev->ft_ds_list) netdev->ft_ds_list = l_queue_new(); l_queue_push_head(netdev->ft_ds_list, info); netdev_send_action_framev(netdev, netdev->handshake->aa, iovs, 2, netdev->frequency, netdev_ft_request_cb, info); return 0; failed: l_free(info); return -EIO; } static void netdev_preauth_cb(const uint8_t *pmk, void *user_data) { struct netdev_preauth_state *preauth = user_data; netdev_preauthenticate_cb_t cb = preauth->cb; preauth->cb = NULL; cb(preauth->netdev, pmk ? NETDEV_RESULT_OK : NETDEV_RESULT_HANDSHAKE_FAILED, pmk, preauth->user_data); } int netdev_preauthenticate(struct netdev *netdev, struct scan_bss *target_bss, netdev_preauthenticate_cb_t cb, void *user_data) { struct netdev_preauth_state *preauth; if (!netdev->operational) return -ENOTCONN; preauth = l_new(struct netdev_preauth_state, 1); if (!eapol_preauth_start(target_bss->addr, netdev->handshake, netdev_preauth_cb, preauth, netdev_preauth_destroy)) { l_free(preauth); return -EIO; } preauth->cb = cb; preauth->user_data = user_data; preauth->netdev = netdev; return 0; } static void netdev_neighbor_report_req_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; if (!netdev->neighbor_report_cb) return; if (l_genl_msg_get_error(msg) < 0) { netdev->neighbor_report_cb(netdev, l_genl_msg_get_error(msg), NULL, 0, netdev->user_data); netdev->neighbor_report_cb = NULL; l_timeout_remove(netdev->neighbor_report_timeout); } } static void netdev_neighbor_report_timeout(struct l_timeout *timeout, void *user_data) { struct netdev *netdev = user_data; netdev->neighbor_report_cb(netdev, -ETIMEDOUT, NULL, 0, netdev->user_data); netdev->neighbor_report_cb = NULL; l_timeout_remove(netdev->neighbor_report_timeout); } int netdev_neighbor_report_req(struct netdev *netdev, netdev_neighbor_report_cb_t cb) { const uint8_t action_frame[] = { 0x05, /* Category: Radio Measurement */ 0x04, /* Radio Measurement Action: Neighbor Report Request */ 0x01, /* Dialog Token: a non-zero value (unused) */ }; if (netdev->neighbor_report_cb || !netdev->connected) return -EBUSY; if (!netdev_send_action_frame(netdev, netdev->handshake->aa, action_frame, sizeof(action_frame), netdev->frequency, netdev_neighbor_report_req_cb, netdev)) return -EIO; netdev->neighbor_report_cb = cb; /* Set a 3-second timeout */ netdev->neighbor_report_timeout = l_timeout_create(3, netdev_neighbor_report_timeout, netdev, NULL); return 0; } static void netdev_neighbor_report_frame_event(const struct mmpdu_header *hdr, const void *body, size_t body_len, int rssi, void *user_data) { struct netdev *netdev = user_data; if (body_len < 3) { l_debug("Neighbor Report frame too short"); return; } if (!netdev->neighbor_report_cb) return; /* * Don't use the dialog token (byte 3), return the first Neighbor * Report Response received. * * Byte 1 is 0x05 for Radio Measurement, byte 2 is 0x05 for * Neighbor Report. */ netdev->neighbor_report_cb(netdev, 0, body + 3, body_len - 3, netdev->user_data); netdev->neighbor_report_cb = NULL; l_timeout_remove(netdev->neighbor_report_timeout); } static void netdev_sa_query_resp_cb(struct l_genl_msg *msg, void *user_data) { int err = l_genl_msg_get_error(msg); const char *ext_error; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_debug("error sending SA Query request: %s", ext_error ? ext_error : strerror(-err)); } static void netdev_sa_query_req_frame_event(const struct mmpdu_header *hdr, const void *body, size_t body_len, int rssi, void *user_data) { uint8_t sa_resp[4]; uint16_t transaction; struct netdev *netdev = user_data; if (body_len < 4) { l_debug("SA Query request too short"); return; } if (!netdev->connected) return; /* only care about SA Queries from our connected AP */ if (memcmp(hdr->address_2, netdev->handshake->aa, 6)) return; transaction = l_get_u16(body + 2); sa_resp[0] = 0x08; /* SA Query */ sa_resp[1] = 0x01; /* Response */ memcpy(sa_resp + 2, &transaction, 2); l_info("received SA Query request from "MAC", transaction=%u", MAC_STR(hdr->address_2), transaction); if (!netdev_send_action_frame(netdev, netdev->handshake->aa, sa_resp, sizeof(sa_resp), netdev->frequency, netdev_sa_query_resp_cb, netdev)) { l_error("error sending SA Query response"); return; } } static void netdev_sa_query_resp_frame_event(const struct mmpdu_header *hdr, const void *body, size_t body_len, int rssi, void *user_data) { struct netdev *netdev = user_data; if (body_len < 4) { l_debug("SA Query frame too short"); return; } l_debug("SA Query src="MAC" dest="MAC" bssid="MAC" transaction=%u", MAC_STR(hdr->address_2), MAC_STR(hdr->address_1), MAC_STR(hdr->address_3), l_get_u16(body + 2)); if (!netdev->sa_query_timeout) { l_debug("no SA Query request sent"); return; } /* check if this is from our connected BSS */ if (memcmp(hdr->address_2, netdev->handshake->aa, 6)) { l_debug("received SA Query from non-connected AP"); return; } if (memcmp(body + 2, &netdev->sa_query_id, 2)) { l_debug("SA Query transaction ID's did not match"); return; } l_info("SA Query response from connected BSS received, " "keeping the connection active"); l_timeout_remove(netdev->sa_query_timeout); netdev->sa_query_timeout = NULL; } static void netdev_sa_query_req_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; int err = l_genl_msg_get_error(msg); const char *ext_error; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_debug("error sending SA Query request: %s", ext_error ? ext_error : strerror(-err)); l_timeout_remove(netdev->sa_query_timeout); netdev->sa_query_timeout = NULL; } static void netdev_sa_query_timeout(struct l_timeout *timeout, void *user_data) { struct netdev *netdev = user_data; struct l_genl_msg *msg; l_info("SA Query timed out, connection is invalid. Disconnecting..."); l_timeout_remove(netdev->sa_query_timeout); netdev->sa_query_timeout = NULL; msg = netdev_build_cmd_disconnect(netdev, MMPDU_REASON_CODE_PREV_AUTH_NOT_VALID); netdev->disconnect_cmd_id = l_genl_family_send(nl80211, msg, netdev_disconnect_cb, netdev, NULL); } static void netdev_unprot_disconnect_event(struct l_genl_msg *msg, struct netdev *netdev) { const struct mmpdu_header *hdr = NULL; struct l_genl_attr attr; uint16_t type; uint16_t len; const void *data; uint8_t action_frame[4]; uint8_t reason_code; if (!netdev->connected) return; /* ignore excessive disassociate requests */ if (netdev->sa_query_timeout) return; if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_FRAME: hdr = mpdu_validate(data, len); break; } } /* check that ATTR_FRAME was actually included */ if (!hdr) return; /* get reason code, first byte of frame */ reason_code = l_get_u8(mmpdu_body(hdr)); l_info("disconnect event, src="MAC" dest="MAC" bssid="MAC" reason=%u", MAC_STR(hdr->address_2), MAC_STR(hdr->address_1), MAC_STR(hdr->address_3), reason_code); if (memcmp(hdr->address_2, netdev->handshake->aa, 6)) { l_debug("received invalid disassociate frame"); return; } if (reason_code != MMPDU_REASON_CODE_CLASS2_FRAME_FROM_NONAUTH_STA && reason_code != MMPDU_REASON_CODE_CLASS3_FRAME_FROM_NONASSOC_STA) { l_debug("invalid reason code %u", reason_code); return; } action_frame[0] = 0x08; /* Category: SA Query */ action_frame[1] = 0x00; /* SA Query Action: Request */ /* Transaction ID */ l_getrandom(action_frame + 2, 2); if (!netdev_send_action_frame(netdev, netdev->handshake->aa, action_frame, sizeof(action_frame), netdev->frequency, netdev_sa_query_req_cb, netdev)) { l_error("error sending SA Query action frame"); return; } netdev->sa_query_id = l_get_u16(action_frame + 2); netdev->sa_query_timeout = l_timeout_create(3, netdev_sa_query_timeout, netdev, NULL); } static void netdev_station_event(struct l_genl_msg *msg, struct netdev *netdev, bool added) { struct l_genl_attr attr; uint16_t type; uint16_t len; const void *data; const uint8_t *mac = NULL; if (netdev_get_iftype(netdev) != NETDEV_IFTYPE_ADHOC) return; if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_MAC: mac = data; break; } } if (!mac) { l_error("%s station event did not include MAC attribute", added ? "new" : "del"); return; } WATCHLIST_NOTIFY(&netdev->station_watches, netdev_station_watch_func_t, netdev, mac, added); } static struct netdev *netdev_from_message(struct l_genl_msg *msg) { uint32_t ifindex; if (nl80211_parse_attrs(msg, NL80211_ATTR_IFINDEX, &ifindex, NL80211_ATTR_UNSPEC) < 0) return NULL; return netdev_find(ifindex); } static void netdev_scan_notify(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev; netdev = netdev_from_message(msg); if (!netdev) return; switch (l_genl_msg_get_command(msg)) { case NL80211_CMD_NEW_SCAN_RESULTS: netdev_new_scan_results_event(msg, netdev); break; } } static bool match_addr(const void *a, const void *b) { const struct scan_bss *bss = a; return memcmp(bss->addr, b, 6) == 0; } static bool netdev_get_fw_scan_cb(int err, struct l_queue *bss_list, const struct scan_freq_set *freqs, void *user_data) { struct netdev *netdev = user_data; struct scan_bss *bss = NULL; /* * If we happened to be disconnected prior to GET_SCAN coming back * just bail out now. This disconnect should already have been handled. */ if (!netdev->connected) return false; if (err < 0) { l_error("Failed to get scan after roam (%d)", err); goto failed; } /* * We don't actually need the entire list since we only provide * station with the roamed BSS. We can remove the BSS we want and by * returning false scan will keep ownership of the list. */ bss = l_queue_remove_if(bss_list, match_addr, netdev->handshake->aa); if (!bss) { l_error("Roam target BSS not found in scan results"); goto failed; } netdev->fw_roam_bss = bss; handshake_state_set_authenticator_ie(netdev->handshake, bss->rsne); if (is_offload(netdev->handshake)) { netdev_connect_ok(netdev); return false; } if (netdev->sm) { if (!eapol_start(netdev->sm)) goto failed; } return false; failed: netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_REASON_CODE_UNSPECIFIED); return false; } /* * CMD_ROAM indicates that the driver has already roamed/associated with a new * AP. This event is nearly identical to the CMD_CONNECT event which is why * netdev_connect_event will handle all the parsing of IE's just as it does * normally. * * Using GET_SCAN we can grab all the required scan_bss data, create that object * and provide it to station. * * The current handshake/netdev_handshake objects are reused after being * reset to allow eapol to happen again without it thinking this is a re-key. */ static void netdev_roam_event(struct l_genl_msg *msg, struct netdev *netdev) { struct netdev_handshake_state *nhs = l_container_of(netdev->handshake, struct netdev_handshake_state, super); struct l_genl_attr attr; uint16_t type, len; const void *data; const uint8_t *mac = NULL; l_debug(""); netdev->operational = false; if (!l_genl_attr_init(&attr, msg)) goto failed; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_MAC: mac = data; break; case NL80211_ATTR_REQ_IE: parse_request_ies(netdev, data, len); break; } } if (!mac) { l_error("Failed to parse ATTR_MAC from CMD_ROAM"); goto failed; } /* Handshake completed in firmware, just get the roamed BSS */ if (is_offload(netdev->handshake)) goto get_fw_scan; /* Reset handshake state */ nhs->complete = false; nhs->ptk_installed = false; nhs->gtk_installed = true; nhs->igtk_installed = true; netdev->handshake->ptk_complete = false; get_fw_scan: handshake_state_set_authenticator_address(netdev->handshake, mac); if (!scan_get_firmware_scan(netdev->wdev_id, netdev_get_fw_scan_cb, netdev, NULL)) goto failed; if (netdev->event_filter) netdev->event_filter(netdev, NETDEV_EVENT_ROAMING, NULL, netdev->user_data); return; failed: l_error("Failed to properly handle the ROAM event -- submit logs!"); netdev_connect_failed(netdev, NETDEV_RESULT_ABORTED, MMPDU_REASON_CODE_UNSPECIFIED); } static void netdev_channel_switch_event(struct l_genl_msg *msg, struct netdev *netdev) { struct l_genl_attr attr; uint16_t type, len; const void *data; uint32_t *freq = NULL; l_debug(""); if (!l_genl_attr_init(&attr, msg)) return; while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_WIPHY_FREQ: if (len != 4) continue; freq = (uint32_t *) data; break; } } if (!freq) return; l_debug("Channel switch event, frequency: %u", *freq); netdev->frequency = *freq; if (!netdev->event_filter) return; netdev->event_filter(netdev, NETDEV_EVENT_CHANNEL_SWITCHED, freq, netdev->user_data); } static void netdev_mlme_notify(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = NULL; uint8_t cmd; cmd = l_genl_msg_get_command(msg); l_debug("MLME notification %s(%u)", nl80211cmd_to_string(cmd), cmd); netdev = netdev_from_message(msg); if (!netdev) return; switch (cmd) { case NL80211_CMD_AUTHENTICATE: netdev_authenticate_event(msg, netdev); break; case NL80211_CMD_DEAUTHENTICATE: netdev_deauthenticate_event(msg, netdev); break; case NL80211_CMD_ASSOCIATE: netdev_associate_event(msg, netdev); break; case NL80211_CMD_ROAM: netdev_roam_event(msg, netdev); break; case NL80211_CMD_CH_SWITCH_NOTIFY: netdev_channel_switch_event(msg, netdev); break; case NL80211_CMD_CONNECT: netdev_connect_event(msg, netdev); break; case NL80211_CMD_DISCONNECT: netdev_disconnect_event(msg, netdev); break; case NL80211_CMD_NOTIFY_CQM: netdev_cqm_event(msg, netdev); break; case NL80211_CMD_SET_REKEY_OFFLOAD: netdev_rekey_offload_event(msg, netdev); break; case NL80211_CMD_UNPROT_DEAUTHENTICATE: case NL80211_CMD_UNPROT_DISASSOCIATE: netdev_unprot_disconnect_event(msg, netdev); break; case NL80211_CMD_NEW_STATION: netdev_station_event(msg, netdev, true); break; case NL80211_CMD_DEL_STATION: netdev_station_event(msg, netdev, false); break; } } static void netdev_pae_destroy(void *user_data) { struct netdev *netdev = user_data; netdev->pae_io = NULL; } static bool netdev_pae_read(struct l_io *io, void *user_data) { int fd = l_io_get_fd(io); struct sockaddr_ll sll; socklen_t sll_len; ssize_t bytes; uint8_t frame[IEEE80211_MAX_DATA_LEN]; memset(&sll, 0, sizeof(sll)); sll_len = sizeof(sll); bytes = recvfrom(fd, frame, sizeof(frame), 0, (struct sockaddr *) &sll, &sll_len); if (bytes <= 0) { l_error("EAPoL read socket: %s", strerror(errno)); return false; } if (sll.sll_halen != ETH_ALEN) return true; __eapol_rx_packet(sll.sll_ifindex, sll.sll_addr, ntohs(sll.sll_protocol), frame, bytes, false); return true; } 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); } static struct l_genl_msg *netdev_build_control_port_frame(struct netdev *netdev, const uint8_t *to, uint16_t proto, bool unencrypted, const void *body, size_t body_len) { struct l_genl_msg *msg; msg = l_genl_msg_new_sized(NL80211_CMD_CONTROL_PORT_FRAME, 128 + body_len); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME, body_len, body); l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, 2, &proto); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, to); if (unencrypted) l_genl_msg_append_attr(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT, 0, NULL); return msg; } static void netdev_control_port_frame_cb(struct l_genl_msg *msg, void *user_data) { int err = l_genl_msg_get_error(msg); const char *ext_error; if (err >= 0) return; ext_error = l_genl_msg_get_extended_error(msg); l_error("CMD_CONTROL_PORT failed: %s", ext_error ? ext_error : 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) 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_CONTROL_PORT_FRAME: netdev_control_port_frame_event(msg, netdev); break; } } int netdev_set_rssi_report_levels(struct netdev *netdev, const int8_t *levels, size_t levels_num) { struct l_genl_msg *cmd_set_cqm; l_debug("ifindex: %d, num_levels: %zu", netdev->index, levels_num); if (levels_num > L_ARRAY_SIZE(netdev->rssi_levels)) return -ENOSPC; if (!wiphy_has_ext_feature(netdev->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST)) goto done; cmd_set_cqm = netdev_build_cmd_cqm_rssi_update(netdev, levels, levels_num); if (!cmd_set_cqm) return -EINVAL; if (!l_genl_family_send(nl80211, cmd_set_cqm, netdev_cmd_set_cqm_cb, NULL, NULL)) { l_genl_msg_unref(cmd_set_cqm); return -EIO; } done: if (levels_num) memcpy(netdev->rssi_levels, levels, levels_num); netdev->rssi_levels_num = levels_num; netdev_rssi_level_init(netdev); netdev_rssi_polling_update(netdev); return 0; } static void netdev_get_station_cb(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev = user_data; struct l_genl_attr attr, nested; uint16_t type, len; const void *data; struct diagnostic_station_info info; netdev->get_station_cmd_id = 0; if (!l_genl_attr_init(&attr, msg)) goto parse_error; memset(&info, 0, sizeof(info)); while (l_genl_attr_next(&attr, &type, &len, &data)) { switch (type) { case NL80211_ATTR_STA_INFO: if (!l_genl_attr_recurse(&attr, &nested)) goto parse_error; if (!netdev_parse_sta_info(&nested, &info)) goto parse_error; break; case NL80211_ATTR_MAC: if (len != 6) goto parse_error; memcpy(info.addr, data, 6); break; } } if (netdev->get_station_cb) netdev->get_station_cb(&info, netdev->get_station_data); return; parse_error: if (netdev->get_station_cb) netdev->get_station_cb(NULL, netdev->get_station_data); } static void netdev_get_station_destroy(void *user_data) { struct netdev *netdev = user_data; netdev->get_station_cmd_id = 0; if (netdev->get_station_destroy) netdev->get_station_destroy(netdev->get_station_data); } int netdev_get_station(struct netdev *netdev, const uint8_t *mac, netdev_get_station_cb_t cb, void *user_data, netdev_destroy_func_t destroy) { struct l_genl_msg *msg; if (netdev->get_station_cmd_id) return -EBUSY; msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, mac); netdev->get_station_cmd_id = l_genl_family_send(nl80211, msg, netdev_get_station_cb, netdev, netdev_get_station_destroy); if (!netdev->get_station_cmd_id) { l_genl_msg_unref(msg); return -EIO; } netdev->get_station_cb = cb; netdev->get_station_data = user_data; netdev->get_station_destroy = destroy; return 0; } int netdev_get_current_station(struct netdev *netdev, netdev_get_station_cb_t cb, void *user_data, netdev_destroy_func_t destroy) { if (!netdev->handshake) return -ENOTCONN; return netdev_get_station(netdev, netdev->handshake->aa, cb, user_data, destroy); } int netdev_get_all_stations(struct netdev *netdev, netdev_get_station_cb_t cb, void *user_data, netdev_destroy_func_t destroy) { struct l_genl_msg *msg; if (netdev->get_station_cmd_id) return -EBUSY; msg = l_genl_msg_new_sized(NL80211_CMD_GET_STATION, 64); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); netdev->get_station_cmd_id = l_genl_family_dump(nl80211, msg, netdev_get_station_cb, netdev, netdev_get_station_destroy); if (!netdev->get_station_cmd_id) { l_genl_msg_unref(msg); return -EIO; } netdev->get_station_cb = cb; netdev->get_station_data = user_data; netdev->get_station_destroy = destroy; return 0; } static void netdev_add_station_frame_watches(struct netdev *netdev) { static const uint8_t action_neighbor_report_prefix[2] = { 0x05, 0x05 }; static const uint8_t action_sa_query_resp_prefix[2] = { 0x08, 0x01 }; static const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 }; static const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 }; static const uint8_t action_qos_map_prefix[] = { 0x01, 0x04 }; uint64_t wdev = netdev->wdev_id; /* Subscribe to Management -> Action -> RM -> Neighbor Report frames */ frame_watch_add(wdev, 0, 0x00d0, action_neighbor_report_prefix, sizeof(action_neighbor_report_prefix), netdev_neighbor_report_frame_event, netdev, NULL); frame_watch_add(wdev, 0, 0x00d0, action_sa_query_resp_prefix, sizeof(action_sa_query_resp_prefix), netdev_sa_query_resp_frame_event, netdev, NULL); frame_watch_add(wdev, 0, 0x00d0, action_sa_query_req_prefix, sizeof(action_sa_query_req_prefix), netdev_sa_query_req_frame_event, netdev, NULL); frame_watch_add(wdev, 0, 0x00d0, action_ft_response_prefix, sizeof(action_ft_response_prefix), netdev_ft_response_frame_event, netdev, NULL); if (wiphy_supports_qos_set_map(netdev->wiphy)) frame_watch_add(wdev, 0, 0x00d0, action_qos_map_prefix, sizeof(action_qos_map_prefix), netdev_qos_map_frame_event, netdev, NULL); } static void netdev_setup_interface(struct netdev *netdev) { switch (netdev->type) { case NL80211_IFTYPE_STATION: netdev_add_station_frame_watches(netdev); break; default: break; } } static void netdev_set_interface_event(struct l_genl_msg *msg, struct netdev *netdev) { uint32_t iftype; uint64_t wdev_id; if (nl80211_parse_attrs(msg, NL80211_ATTR_IFTYPE, &iftype, NL80211_ATTR_WDEV, &wdev_id, NL80211_ATTR_UNSPEC) < 0) return; if (iftype == netdev->type) return; l_debug("Interface type changed from %s to %s", netdev_iftype_to_string(netdev->type), netdev_iftype_to_string(iftype)); netdev->type = iftype; frame_watch_wdev_remove(wdev_id); netdev_setup_interface(netdev); WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_IFTYPE_CHANGE); } static void netdev_config_notify(struct l_genl_msg *msg, void *user_data) { struct netdev *netdev; netdev = netdev_from_message(msg); if (!netdev) return; switch (l_genl_msg_get_command(msg)) { case NL80211_CMD_SET_INTERFACE: netdev_set_interface_event(msg, netdev); break; } } static struct l_genl_msg *netdev_build_cmd_set_interface(struct netdev *netdev, uint32_t iftype) { struct l_genl_msg *msg = l_genl_msg_new_sized(NL80211_CMD_SET_INTERFACE, 32); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_IFTYPE, 4, &iftype); return msg; } struct netdev_set_iftype_request { netdev_command_cb_t cb; void *user_data; netdev_destroy_func_t destroy; uint32_t pending_type; uint32_t ref; struct netdev *netdev; bool bring_up; }; static void netdev_set_iftype_request_destroy(void *user_data) { struct netdev_set_iftype_request *req = user_data; struct netdev *netdev = req->netdev; req->ref--; if (req->ref) return; netdev->set_powered_cmd_id = 0; netdev->set_interface_cmd_id = 0; if (req->destroy) req->destroy(req->user_data); l_free(req); } static void netdev_set_iftype_up_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { struct netdev_set_iftype_request *req = user_data; struct netdev *netdev = req->netdev; if (req->cb) req->cb(netdev, error, req->user_data); } static void netdev_set_iftype_cb(struct l_genl_msg *msg, void *user_data) { struct netdev_set_iftype_request *req = user_data; struct netdev *netdev = req->netdev; int error = l_genl_msg_get_error(msg); if (error != 0) goto done; /* If the netdev was down originally, we're done */ if (!req->bring_up) goto done; netdev->set_powered_cmd_id = l_rtnl_set_powered(rtnl, netdev->index, true, netdev_set_iftype_up_cb, req, netdev_set_iftype_request_destroy); if (!netdev->set_powered_cmd_id) { error = -EIO; goto done; } req->ref++; netdev->set_interface_cmd_id = 0; return; done: if (req->cb) req->cb(netdev, error, req->user_data); } static void netdev_set_iftype_down_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { struct netdev_set_iftype_request *req = user_data; struct netdev *netdev = req->netdev; struct l_genl_msg *msg; if (error != 0) goto error; msg = netdev_build_cmd_set_interface(netdev, req->pending_type); netdev->set_interface_cmd_id = l_genl_family_send(nl80211, msg, netdev_set_iftype_cb, req, netdev_set_iftype_request_destroy); if (!netdev->set_interface_cmd_id) { l_genl_msg_unref(msg); error = -EIO; goto error; } req->ref++; netdev->set_powered_cmd_id = 0; return; error: if (req->cb) req->cb(netdev, error, req->user_data); } int netdev_set_iftype(struct netdev *netdev, enum netdev_iftype type, netdev_command_cb_t cb, void *user_data, netdev_destroy_func_t destroy) { uint32_t iftype; struct netdev_set_iftype_request *req; switch (type) { case NETDEV_IFTYPE_AP: iftype = NL80211_IFTYPE_AP; break; case NETDEV_IFTYPE_ADHOC: iftype = NL80211_IFTYPE_ADHOC; break; case NETDEV_IFTYPE_STATION: iftype = NL80211_IFTYPE_STATION; break; default: l_error("unsupported iftype %u", type); return -EINVAL; } if (netdev->set_powered_cmd_id || netdev->set_interface_cmd_id) return -EBUSY; req = l_new(struct netdev_set_iftype_request, 1); req->cb = cb; req->user_data = user_data; req->destroy = destroy; req->pending_type = iftype; req->netdev = netdev; req->ref = 1; req->bring_up = netdev_get_is_up(netdev); if (!req->bring_up) { struct l_genl_msg *msg = netdev_build_cmd_set_interface(netdev, iftype); netdev->set_interface_cmd_id = l_genl_family_send(nl80211, msg, netdev_set_iftype_cb, req, netdev_set_iftype_request_destroy); if (netdev->set_interface_cmd_id) return 0; l_genl_msg_unref(msg); } else { netdev->set_powered_cmd_id = l_rtnl_set_powered(rtnl, netdev->index, false, netdev_set_iftype_down_cb, req, netdev_set_iftype_request_destroy); if (netdev->set_powered_cmd_id) return 0; } l_free(req); return -EIO; } static void netdev_bridge_port_event(const struct ifinfomsg *ifi, int bytes, bool added) { struct netdev *netdev; struct rtattr *attr; uint32_t master = 0; netdev = netdev_find(ifi->ifi_index); if (!netdev) return; for (attr = IFLA_RTA(ifi); RTA_OK(attr, bytes); attr = RTA_NEXT(attr, bytes)) { switch (attr->rta_type) { case IFLA_MASTER: memcpy(&master, RTA_DATA(attr), sizeof(master)); break; } } l_debug("netdev: %d %s bridge: %d", ifi->ifi_index, (added ? "added to" : "removed from"), master); } struct set_4addr_cb_data { struct netdev *netdev; bool value; netdev_command_cb_t callback; void *user_data; netdev_destroy_func_t destroy; }; static void netdev_set_4addr_cb(struct l_genl_msg *msg, void *user_data) { struct set_4addr_cb_data *cb_data = user_data; int error = l_genl_msg_get_error(msg); if (!cb_data) return; /* cache the value that has just been set */ if (!error) cb_data->netdev->use_4addr = cb_data->value; cb_data->callback(cb_data->netdev, error, cb_data->user_data); } static void netdev_set_4addr_destroy(void *user_data) { struct set_4addr_cb_data *cb_data = user_data; if (!cb_data) return; if (cb_data->destroy) cb_data->destroy(cb_data->user_data); l_free(cb_data); } int netdev_set_4addr(struct netdev *netdev, bool use_4addr, netdev_command_cb_t cb, void *user_data, netdev_destroy_func_t destroy) { struct set_4addr_cb_data *cb_data = NULL; uint8_t attr_4addr = (use_4addr ? 1 : 0); struct l_genl_msg *msg; l_debug("netdev: %d use_4addr: %d", netdev->index, use_4addr); msg = l_genl_msg_new_sized(NL80211_CMD_SET_INTERFACE, 32); l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); l_genl_msg_append_attr(msg, NL80211_ATTR_4ADDR, 1, &attr_4addr); if (cb) { cb_data = l_new(struct set_4addr_cb_data, 1); cb_data->netdev = netdev; cb_data->value = use_4addr; cb_data->callback = cb; cb_data->user_data = user_data; cb_data->destroy = destroy; } if (!l_genl_family_send(nl80211, msg, netdev_set_4addr_cb, cb_data, netdev_set_4addr_destroy)) { l_error("CMD_SET_INTERFACE (4addr) failed"); l_genl_msg_unref(msg); l_free(cb_data); return -EIO; } return 0; } bool netdev_get_4addr(struct netdev *netdev) { return netdev->use_4addr; } static void netdev_newlink_notify(const struct ifinfomsg *ifi, int bytes) { struct netdev *netdev; bool old_up, new_up; char old_name[IFNAMSIZ]; uint8_t old_addr[ETH_ALEN]; struct rtattr *attr; if (ifi->ifi_family == AF_BRIDGE) { netdev_bridge_port_event(ifi, bytes, true); return; } netdev = netdev_find(ifi->ifi_index); if (!netdev) return; old_up = netdev_get_is_up(netdev); strcpy(old_name, netdev->name); memcpy(old_addr, netdev->addr, ETH_ALEN); netdev->ifi_flags = ifi->ifi_flags; 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; memcpy(netdev->addr, RTA_DATA(attr), ETH_ALEN); break; } } if (!netdev->events_ready) /* Did we send NETDEV_WATCH_EVENT_NEW yet? */ return; new_up = netdev_get_is_up(netdev); /* * If mac_change_cmd_id is set we are in the process of changing the * MAC address and this event is a result of powering down/up. In this * case we do not want to emit a netdev DOWN/UP event as this would * cause other modules to behave as such. We do, however, want to emit * address changes so other modules get the new MAC address updated. */ if (old_up != new_up && !netdev->mac_change_cmd_id) WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, new_up ? NETDEV_WATCH_EVENT_UP : NETDEV_WATCH_EVENT_DOWN); if (strcmp(old_name, netdev->name)) WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_NAME_CHANGE); if (memcmp(old_addr, netdev->addr, ETH_ALEN)) WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_ADDRESS_CHANGE); } 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; } 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; } l_rtnl_set_linkmode_and_operstate(rtnl, netdev->index, IF_LINK_MODE_DORMANT, IF_OPER_DOWN, netdev_operstate_cb, L_UINT_TO_PTR(netdev->index), NULL); /* * we don't know the initial status of the 4addr property on this * netdev, therefore we set it to zero by default. */ netdev_set_4addr(netdev, netdev->use_4addr, NULL, NULL, NULL); l_debug("Interface %i initialized", netdev->index); scan_wdev_add(netdev->wdev_id); WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t, netdev, NETDEV_WATCH_EVENT_NEW); netdev->events_ready = true; } static bool netdev_check_set_mac(struct netdev *netdev) { if (l_memeqzero(netdev->set_mac_once, 6)) return false; l_debug("Setting initial address on ifindex: %d to: " MAC, netdev->index, MAC_STR(netdev->set_mac_once)); netdev->set_powered_cmd_id = l_rtnl_set_mac(rtnl, netdev->index, netdev->set_mac_once, true, netdev_initial_up_cb, netdev, NULL); memset(netdev->set_mac_once, 0, 6); return true; } static void netdev_initial_down_cb(int error, uint16_t type, const void *data, uint32_t len, void *user_data) { struct netdev *netdev = user_data; if (!error) netdev->ifi_flags &= ~IFF_UP; else { l_error("Error taking interface %i down: %s", netdev->index, strerror(-error)); netdev->set_powered_cmd_id = 0; return; } if (netdev_check_set_mac(netdev)) return; netdev->set_powered_cmd_id = l_rtnl_set_powered(rtnl, netdev->index, true, netdev_initial_up_cb, netdev, NULL); } 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; 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); return; } netdev = netdev_find(ifi->ifi_index); if (!netdev) return; bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg)); netdev_newlink_notify(ifi, bytes); /* * If the interface is UP, reset it to ensure a clean state. * Otherwise, if we need to set a random mac, do so. If not, just * bring the interface UP. */ powered = netdev_get_is_up(netdev); if (!powered && netdev_check_set_mac(netdev)) return; cb = powered ? netdev_initial_down_cb : netdev_initial_up_cb; netdev->set_powered_cmd_id = l_rtnl_set_powered(rtnl, ifi->ifi_index, !powered, cb, netdev, NULL); } static struct l_io *pae_open(uint32_t ifindex) { /* * BPF filter to match skb->dev->type == 1 (ARPHRD_ETHER) and * match skb->protocol == 0x888e (PAE) or 0x88c7 (preauthentication). */ struct sock_filter pae_filter[] = { { 0x20, 0, 0, 0xfffff008 }, /* ld #ifidx */ { 0x15, 0, 6, 0x00000000 }, /* jne #0, drop */ { 0x28, 0, 0, 0xfffff01c }, /* ldh #hatype */ { 0x15, 0, 4, 0x00000001 }, /* jne #1, drop */ { 0x28, 0, 0, 0xfffff000 }, /* ldh #proto */ { 0x15, 1, 0, 0x0000888e }, /* je #0x888e, keep */ { 0x15, 0, 1, 0x000088c7 }, /* jne #0x88c7, drop */ { 0x06, 0, 0, 0xffffffff }, /* keep: ret #-1 */ { 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */ }; const struct sock_fprog pae_fprog = { .len = L_ARRAY_SIZE(pae_filter), .filter = pae_filter }; struct l_io *io; int fd; fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, htons(ETH_P_ALL)); if (fd < 0) return NULL; /* * Here we modify the k value in the BPF program above to match the * given ifindex. We do it this way instead of using bind to attach * to a specific interface index to avoid having to re-open the fd * whenever the device is powered down / up */ pae_filter[1].k = ifindex; if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &pae_fprog, sizeof(pae_fprog)) < 0) goto error; io = l_io_new(fd); l_io_set_close_on_destroy(io, true); return io; error: close(fd); return NULL; } struct netdev *netdev_create_from_genl(struct l_genl_msg *msg, const uint8_t *set_mac) { const char *ifname; const uint8_t *ifaddr; uint32_t ifindex; uint32_t iftype; uint64_t wdev; uint32_t wiphy_id; struct netdev *netdev; struct wiphy *wiphy = NULL; struct ifinfomsg *rtmmsg; size_t bufsize; struct l_io *pae_io = NULL; if (nl80211_parse_attrs(msg, NL80211_ATTR_IFINDEX, &ifindex, NL80211_ATTR_WDEV, &wdev, NL80211_ATTR_IFNAME, &ifname, NL80211_ATTR_WIPHY, &wiphy_id, NL80211_ATTR_IFTYPE, &iftype, NL80211_ATTR_MAC, &ifaddr, NL80211_ATTR_UNSPEC) < 0) { l_warn("Required attributes missing"); return NULL; } wiphy = wiphy_find(wiphy_id); if (!wiphy) { l_warn("No wiphy: %d", wiphy_id); 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; netdev->wdev_id = wdev; netdev->type = iftype; netdev->rekey_offload_support = true; memcpy(netdev->addr, ifaddr, sizeof(netdev->addr)); l_strlcpy(netdev->name, ifname, IFNAMSIZ); netdev->wiphy = wiphy; netdev->pae_over_nl80211 = pae_io == NULL; if (set_mac) memcpy(netdev->set_mac_once, set_mac, 6); if (pae_io) { netdev->pae_io = pae_io; l_io_set_read_handler(netdev->pae_io, netdev_pae_read, netdev, netdev_pae_destroy); } watchlist_init(&netdev->station_watches, NULL); l_queue_push_tail(netdev_list, netdev); l_debug("Created interface %s[%d %" PRIx64 "]", netdev->name, netdev->index, netdev->wdev_id); /* Query interface flags */ bufsize = NLMSG_ALIGN(sizeof(struct ifinfomsg)); 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, netdev_getlink_cb, NULL, NULL); l_free(rtmmsg); netdev_setup_interface(netdev); return netdev; } bool netdev_destroy(struct netdev *netdev) { if (!l_queue_remove(netdev_list, netdev)) return false; netdev_free(netdev); return true; } 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); bytes = len - NLMSG_ALIGN(sizeof(struct ifinfomsg)); switch (type) { case RTM_NEWLINK: netdev_newlink_notify(ifi, bytes); break; case RTM_DELLINK: netdev_dellink_notify(ifi, bytes); break; } } uint32_t netdev_station_watch_add(struct netdev *netdev, netdev_station_watch_func_t func, void *user_data) { return watchlist_add(&netdev->station_watches, func, user_data, NULL); } bool netdev_station_watch_remove(struct netdev *netdev, uint32_t id) { return watchlist_remove(&netdev->station_watches, id); } 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) { struct l_genl *genl = iwd_get_genl(); const struct l_settings *settings = iwd_get_config(); const char *rand_addr_str; if (rtnl) return -EALREADY; rtnl = iwd_get_rtnl(); 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; } if (!l_settings_get_int(settings, "General", "RoamThreshold", &LOW_SIGNAL_THRESHOLD)) LOW_SIGNAL_THRESHOLD = -70; if (!l_settings_get_int(settings, "General", "RoamThreshold5G", &LOW_SIGNAL_THRESHOLD_5GHZ)) LOW_SIGNAL_THRESHOLD_5GHZ = -76; if (!l_settings_get_bool(settings, "General", "ControlPortOverNL80211", &pae_over_nl80211)) pae_over_nl80211 = true; rand_addr_str = l_settings_get_value(settings, "General", "AddressRandomization"); if (rand_addr_str && !strcmp(rand_addr_str, "network")) mac_per_ssid = true; watchlist_init(&netdev_watches, NULL); netdev_list = l_queue_new(); __handshake_set_install_tk_func(netdev_set_tk); __handshake_set_install_gtk_func(netdev_set_gtk); __handshake_set_install_igtk_func(netdev_set_igtk); __eapol_set_rekey_offload_func(netdev_set_rekey_offload); __eapol_set_tx_packet_func(netdev_control_port_frame); __eapol_set_install_pmk_func(netdev_set_pmk); unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME, netdev_unicast_notify, NULL, NULL); if (!unicast_watch) l_error("Registering for unicast notification failed"); if (!l_genl_family_register(nl80211, "mlme", netdev_mlme_notify, NULL, NULL)) l_error("Registering for MLME notification failed"); if (!l_genl_family_register(nl80211, "scan", netdev_scan_notify, NULL, NULL)) l_error("Registering for scan notifications failed"); if (!l_genl_family_register(nl80211, "config", netdev_config_notify, NULL, NULL)) l_error("Registering for config notifications failed"); return 0; fail_netlink: rtnl = NULL; return -EIO; } static void netdev_exit(void) { struct l_genl *genl = iwd_get_genl(); if (!rtnl) return; l_genl_remove_unicast_watch(genl, unicast_watch); watchlist_destroy(&netdev_watches); l_queue_destroy(netdev_list, netdev_free); netdev_list = NULL; l_genl_family_free(nl80211); nl80211 = NULL; rtnl = NULL; } void netdev_shutdown(void) { struct netdev *netdev; if (!rtnl) return; l_queue_foreach(netdev_list, netdev_shutdown_one, NULL); while ((netdev = l_queue_peek_head(netdev_list))) { netdev_free(netdev); l_queue_pop_head(netdev_list); } } IWD_MODULE(netdev, netdev_init, netdev_exit); IWD_MODULE_DEPENDS(netdev, eapol);