| /* |
| * Driver interaction with Linux nl80211/cfg80211 |
| * Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi> |
| * Copyright (c) 2003-2004, Instant802 Networks, Inc. |
| * Copyright (c) 2005-2006, Devicescape Software, Inc. |
| * Copyright (c) 2007, Johannes Berg <johannes@sipsolutions.net> |
| * Copyright (c) 2009-2010, Atheros Communications |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <net/if.h> |
| #include <netlink/genl/genl.h> |
| #include <netlink/genl/ctrl.h> |
| #ifdef CONFIG_LIBNL3_ROUTE |
| #include <netlink/route/neighbour.h> |
| #endif /* CONFIG_LIBNL3_ROUTE */ |
| #include <linux/rtnetlink.h> |
| #include <netpacket/packet.h> |
| #include <linux/errqueue.h> |
| |
| #include "common.h" |
| #include "eloop.h" |
| #include "common/qca-vendor.h" |
| #include "common/qca-vendor-attr.h" |
| #include "common/ieee802_11_defs.h" |
| #include "common/ieee802_11_common.h" |
| #include "l2_packet/l2_packet.h" |
| #include "netlink.h" |
| #include "linux_defines.h" |
| #include "linux_ioctl.h" |
| #include "radiotap.h" |
| #include "radiotap_iter.h" |
| #include "rfkill.h" |
| #include "driver_nl80211.h" |
| |
| |
| #ifndef CONFIG_LIBNL20 |
| /* |
| * libnl 1.1 has a bug, it tries to allocate socket numbers densely |
| * but when you free a socket again it will mess up its bitmap and |
| * and use the wrong number the next time it needs a socket ID. |
| * Therefore, we wrap the handle alloc/destroy and add our own pid |
| * accounting. |
| */ |
| static uint32_t port_bitmap[32] = { 0 }; |
| |
| static struct nl_handle *nl80211_handle_alloc(void *cb) |
| { |
| struct nl_handle *handle; |
| uint32_t pid = getpid() & 0x3FFFFF; |
| int i; |
| |
| handle = nl_handle_alloc_cb(cb); |
| |
| for (i = 0; i < 1024; i++) { |
| if (port_bitmap[i / 32] & (1 << (i % 32))) |
| continue; |
| port_bitmap[i / 32] |= 1 << (i % 32); |
| pid += i << 22; |
| break; |
| } |
| |
| nl_socket_set_local_port(handle, pid); |
| |
| return handle; |
| } |
| |
| static void nl80211_handle_destroy(struct nl_handle *handle) |
| { |
| uint32_t port = nl_socket_get_local_port(handle); |
| |
| port >>= 22; |
| port_bitmap[port / 32] &= ~(1 << (port % 32)); |
| |
| nl_handle_destroy(handle); |
| } |
| #endif /* CONFIG_LIBNL20 */ |
| |
| |
| #ifdef ANDROID |
| /* system/core/libnl_2 does not include nl_socket_set_nonblocking() */ |
| #undef nl_socket_set_nonblocking |
| #define nl_socket_set_nonblocking(h) android_nl_socket_set_nonblocking(h) |
| |
| #define genl_ctrl_resolve android_genl_ctrl_resolve |
| #endif /* ANDROID */ |
| |
| |
| static struct nl_handle * nl_create_handle(struct nl_cb *cb, const char *dbg) |
| { |
| struct nl_handle *handle; |
| |
| handle = nl80211_handle_alloc(cb); |
| if (handle == NULL) { |
| wpa_printf(MSG_ERROR, "nl80211: Failed to allocate netlink " |
| "callbacks (%s)", dbg); |
| return NULL; |
| } |
| |
| if (genl_connect(handle)) { |
| wpa_printf(MSG_ERROR, "nl80211: Failed to connect to generic " |
| "netlink (%s)", dbg); |
| nl80211_handle_destroy(handle); |
| return NULL; |
| } |
| |
| return handle; |
| } |
| |
| |
| static void nl_destroy_handles(struct nl_handle **handle) |
| { |
| if (*handle == NULL) |
| return; |
| nl80211_handle_destroy(*handle); |
| *handle = NULL; |
| } |
| |
| |
| #if __WORDSIZE == 64 |
| #define ELOOP_SOCKET_INVALID (intptr_t) 0x8888888888888889ULL |
| #else |
| #define ELOOP_SOCKET_INVALID (intptr_t) 0x88888889ULL |
| #endif |
| |
| static void nl80211_register_eloop_read(struct nl_handle **handle, |
| eloop_sock_handler handler, |
| void *eloop_data) |
| { |
| #ifdef CONFIG_LIBNL20 |
| /* |
| * libnl uses a pretty small buffer (32 kB that gets converted to 64 kB) |
| * by default. It is possible to hit that limit in some cases where |
| * operations are blocked, e.g., with a burst of Deauthentication frames |
| * to hostapd and STA entry deletion. Try to increase the buffer to make |
| * this less likely to occur. |
| */ |
| if (nl_socket_set_buffer_size(*handle, 262144, 0) < 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Could not set nl_socket RX buffer size: %s", |
| strerror(errno)); |
| /* continue anyway with the default (smaller) buffer */ |
| } |
| #endif /* CONFIG_LIBNL20 */ |
| |
| nl_socket_set_nonblocking(*handle); |
| eloop_register_read_sock(nl_socket_get_fd(*handle), handler, |
| eloop_data, *handle); |
| *handle = (void *) (((intptr_t) *handle) ^ ELOOP_SOCKET_INVALID); |
| } |
| |
| |
| static void nl80211_destroy_eloop_handle(struct nl_handle **handle) |
| { |
| *handle = (void *) (((intptr_t) *handle) ^ ELOOP_SOCKET_INVALID); |
| eloop_unregister_read_sock(nl_socket_get_fd(*handle)); |
| nl_destroy_handles(handle); |
| } |
| |
| |
| static void nl80211_global_deinit(void *priv); |
| static void nl80211_check_global(struct nl80211_global *global); |
| |
| static void wpa_driver_nl80211_deinit(struct i802_bss *bss); |
| static int wpa_driver_nl80211_set_mode_ibss(struct i802_bss *bss, |
| struct hostapd_freq_params *freq); |
| |
| static int |
| wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv, |
| const u8 *set_addr, int first, |
| const char *driver_params); |
| static int nl80211_send_frame_cmd(struct i802_bss *bss, |
| unsigned int freq, unsigned int wait, |
| const u8 *buf, size_t buf_len, u64 *cookie, |
| int no_cck, int no_ack, int offchanok); |
| static int wpa_driver_nl80211_probe_req_report(struct i802_bss *bss, |
| int report); |
| |
| static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx); |
| static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx); |
| static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx); |
| |
| static int nl80211_set_channel(struct i802_bss *bss, |
| struct hostapd_freq_params *freq, int set_chan); |
| static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv, |
| int ifindex, int disabled); |
| |
| static int nl80211_leave_ibss(struct wpa_driver_nl80211_data *drv, |
| int reset_mode); |
| |
| static int i802_set_iface_flags(struct i802_bss *bss, int up); |
| static int nl80211_set_param(void *priv, const char *param); |
| |
| |
| /* Converts nl80211_chan_width to a common format */ |
| enum chan_width convert2width(int width) |
| { |
| switch (width) { |
| case NL80211_CHAN_WIDTH_20_NOHT: |
| return CHAN_WIDTH_20_NOHT; |
| case NL80211_CHAN_WIDTH_20: |
| return CHAN_WIDTH_20; |
| case NL80211_CHAN_WIDTH_40: |
| return CHAN_WIDTH_40; |
| case NL80211_CHAN_WIDTH_80: |
| return CHAN_WIDTH_80; |
| case NL80211_CHAN_WIDTH_80P80: |
| return CHAN_WIDTH_80P80; |
| case NL80211_CHAN_WIDTH_160: |
| return CHAN_WIDTH_160; |
| } |
| return CHAN_WIDTH_UNKNOWN; |
| } |
| |
| |
| int is_ap_interface(enum nl80211_iftype nlmode) |
| { |
| return nlmode == NL80211_IFTYPE_AP || |
| nlmode == NL80211_IFTYPE_P2P_GO; |
| } |
| |
| |
| int is_sta_interface(enum nl80211_iftype nlmode) |
| { |
| return nlmode == NL80211_IFTYPE_STATION || |
| nlmode == NL80211_IFTYPE_P2P_CLIENT; |
| } |
| |
| |
| static int is_p2p_net_interface(enum nl80211_iftype nlmode) |
| { |
| return nlmode == NL80211_IFTYPE_P2P_CLIENT || |
| nlmode == NL80211_IFTYPE_P2P_GO; |
| } |
| |
| |
| struct i802_bss * get_bss_ifindex(struct wpa_driver_nl80211_data *drv, |
| int ifindex) |
| { |
| struct i802_bss *bss; |
| |
| for (bss = drv->first_bss; bss; bss = bss->next) { |
| if (bss->ifindex == ifindex) |
| return bss; |
| } |
| |
| return NULL; |
| } |
| |
| |
| static int is_mesh_interface(enum nl80211_iftype nlmode) |
| { |
| return nlmode == NL80211_IFTYPE_MESH_POINT; |
| } |
| |
| |
| void nl80211_mark_disconnected(struct wpa_driver_nl80211_data *drv) |
| { |
| if (drv->associated) |
| os_memcpy(drv->prev_bssid, drv->bssid, ETH_ALEN); |
| drv->associated = 0; |
| os_memset(drv->bssid, 0, ETH_ALEN); |
| } |
| |
| |
| /* nl80211 code */ |
| static int ack_handler(struct nl_msg *msg, void *arg) |
| { |
| int *err = arg; |
| *err = 0; |
| return NL_STOP; |
| } |
| |
| static int finish_handler(struct nl_msg *msg, void *arg) |
| { |
| int *ret = arg; |
| *ret = 0; |
| return NL_SKIP; |
| } |
| |
| static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, |
| void *arg) |
| { |
| int *ret = arg; |
| *ret = err->error; |
| return NL_SKIP; |
| } |
| |
| |
| static int no_seq_check(struct nl_msg *msg, void *arg) |
| { |
| return NL_OK; |
| } |
| |
| |
| static void nl80211_nlmsg_clear(struct nl_msg *msg) |
| { |
| /* |
| * Clear nlmsg data, e.g., to make sure key material is not left in |
| * heap memory for unnecessarily long time. |
| */ |
| if (msg) { |
| struct nlmsghdr *hdr = nlmsg_hdr(msg); |
| void *data = nlmsg_data(hdr); |
| /* |
| * This would use nlmsg_datalen() or the older nlmsg_len() if |
| * only libnl were to maintain a stable API.. Neither will work |
| * with all released versions, so just calculate the length |
| * here. |
| */ |
| int len = hdr->nlmsg_len - NLMSG_HDRLEN; |
| |
| os_memset(data, 0, len); |
| } |
| } |
| |
| |
| static int send_and_recv(struct nl80211_global *global, |
| struct nl_handle *nl_handle, struct nl_msg *msg, |
| int (*valid_handler)(struct nl_msg *, void *), |
| void *valid_data) |
| { |
| struct nl_cb *cb; |
| int err = -ENOMEM; |
| |
| if (!msg) |
| return -ENOMEM; |
| |
| cb = nl_cb_clone(global->nl_cb); |
| if (!cb) |
| goto out; |
| |
| err = nl_send_auto_complete(nl_handle, msg); |
| if (err < 0) |
| goto out; |
| |
| err = 1; |
| |
| nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); |
| nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); |
| |
| if (valid_handler) |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, |
| valid_handler, valid_data); |
| |
| while (err > 0) { |
| int res = nl_recvmsgs(nl_handle, cb); |
| if (res < 0) { |
| wpa_printf(MSG_INFO, |
| "nl80211: %s->nl_recvmsgs failed: %d", |
| __func__, res); |
| } |
| } |
| out: |
| nl_cb_put(cb); |
| if (!valid_handler && valid_data == (void *) -1) |
| nl80211_nlmsg_clear(msg); |
| nlmsg_free(msg); |
| return err; |
| } |
| |
| |
| int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv, |
| struct nl_msg *msg, |
| int (*valid_handler)(struct nl_msg *, void *), |
| void *valid_data) |
| { |
| return send_and_recv(drv->global, drv->global->nl, msg, |
| valid_handler, valid_data); |
| } |
| |
| |
| struct family_data { |
| const char *group; |
| int id; |
| }; |
| |
| |
| static int family_handler(struct nl_msg *msg, void *arg) |
| { |
| struct family_data *res = arg; |
| struct nlattr *tb[CTRL_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *mcgrp; |
| int i; |
| |
| nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb[CTRL_ATTR_MCAST_GROUPS]) |
| return NL_SKIP; |
| |
| nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], i) { |
| struct nlattr *tb2[CTRL_ATTR_MCAST_GRP_MAX + 1]; |
| nla_parse(tb2, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), |
| nla_len(mcgrp), NULL); |
| if (!tb2[CTRL_ATTR_MCAST_GRP_NAME] || |
| !tb2[CTRL_ATTR_MCAST_GRP_ID] || |
| os_strncmp(nla_data(tb2[CTRL_ATTR_MCAST_GRP_NAME]), |
| res->group, |
| nla_len(tb2[CTRL_ATTR_MCAST_GRP_NAME])) != 0) |
| continue; |
| res->id = nla_get_u32(tb2[CTRL_ATTR_MCAST_GRP_ID]); |
| break; |
| }; |
| |
| return NL_SKIP; |
| } |
| |
| |
| static int nl_get_multicast_id(struct nl80211_global *global, |
| const char *family, const char *group) |
| { |
| struct nl_msg *msg; |
| int ret; |
| struct family_data res = { group, -ENOENT }; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -ENOMEM; |
| if (!genlmsg_put(msg, 0, 0, genl_ctrl_resolve(global->nl, "nlctrl"), |
| 0, 0, CTRL_CMD_GETFAMILY, 0) || |
| nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family)) { |
| nlmsg_free(msg); |
| return -1; |
| } |
| |
| ret = send_and_recv(global, global->nl, msg, family_handler, &res); |
| if (ret == 0) |
| ret = res.id; |
| return ret; |
| } |
| |
| |
| void * nl80211_cmd(struct wpa_driver_nl80211_data *drv, |
| struct nl_msg *msg, int flags, uint8_t cmd) |
| { |
| return genlmsg_put(msg, 0, 0, drv->global->nl80211_id, |
| 0, flags, cmd, 0); |
| } |
| |
| |
| static int nl80211_set_iface_id(struct nl_msg *msg, struct i802_bss *bss) |
| { |
| if (bss->wdev_id_set) |
| return nla_put_u64(msg, NL80211_ATTR_WDEV, bss->wdev_id); |
| return nla_put_u32(msg, NL80211_ATTR_IFINDEX, bss->ifindex); |
| } |
| |
| |
| struct nl_msg * nl80211_cmd_msg(struct i802_bss *bss, int flags, uint8_t cmd) |
| { |
| struct nl_msg *msg; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return NULL; |
| |
| if (!nl80211_cmd(bss->drv, msg, flags, cmd) || |
| nl80211_set_iface_id(msg, bss) < 0) { |
| nlmsg_free(msg); |
| return NULL; |
| } |
| |
| return msg; |
| } |
| |
| |
| static struct nl_msg * |
| nl80211_ifindex_msg(struct wpa_driver_nl80211_data *drv, int ifindex, |
| int flags, uint8_t cmd) |
| { |
| struct nl_msg *msg; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return NULL; |
| |
| if (!nl80211_cmd(drv, msg, flags, cmd) || |
| nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex)) { |
| nlmsg_free(msg); |
| return NULL; |
| } |
| |
| return msg; |
| } |
| |
| |
| struct nl_msg * nl80211_drv_msg(struct wpa_driver_nl80211_data *drv, int flags, |
| uint8_t cmd) |
| { |
| return nl80211_ifindex_msg(drv, drv->ifindex, flags, cmd); |
| } |
| |
| |
| struct nl_msg * nl80211_bss_msg(struct i802_bss *bss, int flags, uint8_t cmd) |
| { |
| return nl80211_ifindex_msg(bss->drv, bss->ifindex, flags, cmd); |
| } |
| |
| |
| struct wiphy_idx_data { |
| int wiphy_idx; |
| enum nl80211_iftype nlmode; |
| u8 *macaddr; |
| }; |
| |
| |
| static int netdev_info_handler(struct nl_msg *msg, void *arg) |
| { |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct wiphy_idx_data *info = arg; |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (tb[NL80211_ATTR_WIPHY]) |
| info->wiphy_idx = nla_get_u32(tb[NL80211_ATTR_WIPHY]); |
| |
| if (tb[NL80211_ATTR_IFTYPE]) |
| info->nlmode = nla_get_u32(tb[NL80211_ATTR_IFTYPE]); |
| |
| if (tb[NL80211_ATTR_MAC] && info->macaddr) |
| os_memcpy(info->macaddr, nla_data(tb[NL80211_ATTR_MAC]), |
| ETH_ALEN); |
| |
| return NL_SKIP; |
| } |
| |
| |
| int nl80211_get_wiphy_index(struct i802_bss *bss) |
| { |
| struct nl_msg *msg; |
| struct wiphy_idx_data data = { |
| .wiphy_idx = -1, |
| .macaddr = NULL, |
| }; |
| |
| if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE))) |
| return -1; |
| |
| if (send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data) == 0) |
| return data.wiphy_idx; |
| return -1; |
| } |
| |
| |
| static enum nl80211_iftype nl80211_get_ifmode(struct i802_bss *bss) |
| { |
| struct nl_msg *msg; |
| struct wiphy_idx_data data = { |
| .nlmode = NL80211_IFTYPE_UNSPECIFIED, |
| .macaddr = NULL, |
| }; |
| |
| if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE))) |
| return NL80211_IFTYPE_UNSPECIFIED; |
| |
| if (send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data) == 0) |
| return data.nlmode; |
| return NL80211_IFTYPE_UNSPECIFIED; |
| } |
| |
| |
| static int nl80211_get_macaddr(struct i802_bss *bss) |
| { |
| struct nl_msg *msg; |
| struct wiphy_idx_data data = { |
| .macaddr = bss->addr, |
| }; |
| |
| if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE))) |
| return -1; |
| |
| return send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data); |
| } |
| |
| |
| static int nl80211_register_beacons(struct wpa_driver_nl80211_data *drv, |
| struct nl80211_wiphy_data *w) |
| { |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -1; |
| |
| if (!nl80211_cmd(drv, msg, 0, NL80211_CMD_REGISTER_BEACONS) || |
| nla_put_u32(msg, NL80211_ATTR_WIPHY, w->wiphy_idx)) { |
| nlmsg_free(msg); |
| return -1; |
| } |
| |
| ret = send_and_recv(drv->global, w->nl_beacons, msg, NULL, NULL); |
| if (ret) { |
| wpa_printf(MSG_DEBUG, "nl80211: Register beacons command " |
| "failed: ret=%d (%s)", |
| ret, strerror(-ret)); |
| } |
| return ret; |
| } |
| |
| |
| static void nl80211_recv_beacons(int sock, void *eloop_ctx, void *handle) |
| { |
| struct nl80211_wiphy_data *w = eloop_ctx; |
| int res; |
| |
| wpa_printf(MSG_EXCESSIVE, "nl80211: Beacon event message available"); |
| |
| res = nl_recvmsgs(handle, w->nl_cb); |
| if (res < 0) { |
| wpa_printf(MSG_INFO, "nl80211: %s->nl_recvmsgs failed: %d", |
| __func__, res); |
| } |
| } |
| |
| |
| static int process_beacon_event(struct nl_msg *msg, void *arg) |
| { |
| struct nl80211_wiphy_data *w = arg; |
| struct wpa_driver_nl80211_data *drv; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| union wpa_event_data event; |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (gnlh->cmd != NL80211_CMD_FRAME) { |
| wpa_printf(MSG_DEBUG, "nl80211: Unexpected beacon event? (%d)", |
| gnlh->cmd); |
| return NL_SKIP; |
| } |
| |
| if (!tb[NL80211_ATTR_FRAME]) |
| return NL_SKIP; |
| |
| dl_list_for_each(drv, &w->drvs, struct wpa_driver_nl80211_data, |
| wiphy_list) { |
| os_memset(&event, 0, sizeof(event)); |
| event.rx_mgmt.frame = nla_data(tb[NL80211_ATTR_FRAME]); |
| event.rx_mgmt.frame_len = nla_len(tb[NL80211_ATTR_FRAME]); |
| wpa_supplicant_event(drv->ctx, EVENT_RX_MGMT, &event); |
| } |
| |
| return NL_SKIP; |
| } |
| |
| |
| static struct nl80211_wiphy_data * |
| nl80211_get_wiphy_data_ap(struct i802_bss *bss) |
| { |
| static DEFINE_DL_LIST(nl80211_wiphys); |
| struct nl80211_wiphy_data *w; |
| int wiphy_idx, found = 0; |
| struct i802_bss *tmp_bss; |
| |
| if (bss->wiphy_data != NULL) |
| return bss->wiphy_data; |
| |
| wiphy_idx = nl80211_get_wiphy_index(bss); |
| |
| dl_list_for_each(w, &nl80211_wiphys, struct nl80211_wiphy_data, list) { |
| if (w->wiphy_idx == wiphy_idx) |
| goto add; |
| } |
| |
| /* alloc new one */ |
| w = os_zalloc(sizeof(*w)); |
| if (w == NULL) |
| return NULL; |
| w->wiphy_idx = wiphy_idx; |
| dl_list_init(&w->bsss); |
| dl_list_init(&w->drvs); |
| |
| w->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!w->nl_cb) { |
| os_free(w); |
| return NULL; |
| } |
| nl_cb_set(w->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); |
| nl_cb_set(w->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, process_beacon_event, |
| w); |
| |
| w->nl_beacons = nl_create_handle(bss->drv->global->nl_cb, |
| "wiphy beacons"); |
| if (w->nl_beacons == NULL) { |
| os_free(w); |
| return NULL; |
| } |
| |
| if (nl80211_register_beacons(bss->drv, w)) { |
| nl_destroy_handles(&w->nl_beacons); |
| os_free(w); |
| return NULL; |
| } |
| |
| nl80211_register_eloop_read(&w->nl_beacons, nl80211_recv_beacons, w); |
| |
| dl_list_add(&nl80211_wiphys, &w->list); |
| |
| add: |
| /* drv entry for this bss already there? */ |
| dl_list_for_each(tmp_bss, &w->bsss, struct i802_bss, wiphy_list) { |
| if (tmp_bss->drv == bss->drv) { |
| found = 1; |
| break; |
| } |
| } |
| /* if not add it */ |
| if (!found) |
| dl_list_add(&w->drvs, &bss->drv->wiphy_list); |
| |
| dl_list_add(&w->bsss, &bss->wiphy_list); |
| bss->wiphy_data = w; |
| return w; |
| } |
| |
| |
| static void nl80211_put_wiphy_data_ap(struct i802_bss *bss) |
| { |
| struct nl80211_wiphy_data *w = bss->wiphy_data; |
| struct i802_bss *tmp_bss; |
| int found = 0; |
| |
| if (w == NULL) |
| return; |
| bss->wiphy_data = NULL; |
| dl_list_del(&bss->wiphy_list); |
| |
| /* still any for this drv present? */ |
| dl_list_for_each(tmp_bss, &w->bsss, struct i802_bss, wiphy_list) { |
| if (tmp_bss->drv == bss->drv) { |
| found = 1; |
| break; |
| } |
| } |
| /* if not remove it */ |
| if (!found) |
| dl_list_del(&bss->drv->wiphy_list); |
| |
| if (!dl_list_empty(&w->bsss)) |
| return; |
| |
| nl80211_destroy_eloop_handle(&w->nl_beacons); |
| |
| nl_cb_put(w->nl_cb); |
| dl_list_del(&w->list); |
| os_free(w); |
| } |
| |
| |
| static int wpa_driver_nl80211_get_bssid(void *priv, u8 *bssid) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| if (!drv->associated) |
| return -1; |
| os_memcpy(bssid, drv->bssid, ETH_ALEN); |
| return 0; |
| } |
| |
| |
| static int wpa_driver_nl80211_get_ssid(void *priv, u8 *ssid) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| if (!drv->associated) |
| return -1; |
| os_memcpy(ssid, drv->ssid, drv->ssid_len); |
| return drv->ssid_len; |
| } |
| |
| |
| static void wpa_driver_nl80211_event_newlink( |
| struct wpa_driver_nl80211_data *drv, const char *ifname) |
| { |
| union wpa_event_data event; |
| |
| if (os_strcmp(drv->first_bss->ifname, ifname) == 0) { |
| if (if_nametoindex(drv->first_bss->ifname) == 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Interface %s does not exist - ignore RTM_NEWLINK", |
| drv->first_bss->ifname); |
| return; |
| } |
| if (!drv->if_removed) |
| return; |
| wpa_printf(MSG_DEBUG, "nl80211: Mark if_removed=0 for %s based on RTM_NEWLINK event", |
| drv->first_bss->ifname); |
| drv->if_removed = 0; |
| } |
| |
| os_memset(&event, 0, sizeof(event)); |
| os_strlcpy(event.interface_status.ifname, ifname, |
| sizeof(event.interface_status.ifname)); |
| event.interface_status.ievent = EVENT_INTERFACE_ADDED; |
| wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_STATUS, &event); |
| } |
| |
| |
| static void wpa_driver_nl80211_event_dellink( |
| struct wpa_driver_nl80211_data *drv, const char *ifname) |
| { |
| union wpa_event_data event; |
| |
| if (os_strcmp(drv->first_bss->ifname, ifname) == 0) { |
| if (drv->if_removed) { |
| wpa_printf(MSG_DEBUG, "nl80211: if_removed already set - ignore RTM_DELLINK event for %s", |
| ifname); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "RTM_DELLINK: Interface '%s' removed - mark if_removed=1", |
| ifname); |
| drv->if_removed = 1; |
| } else { |
| wpa_printf(MSG_DEBUG, "RTM_DELLINK: Interface '%s' removed", |
| ifname); |
| } |
| |
| os_memset(&event, 0, sizeof(event)); |
| os_strlcpy(event.interface_status.ifname, ifname, |
| sizeof(event.interface_status.ifname)); |
| event.interface_status.ievent = EVENT_INTERFACE_REMOVED; |
| wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_STATUS, &event); |
| } |
| |
| |
| static int wpa_driver_nl80211_own_ifname(struct wpa_driver_nl80211_data *drv, |
| u8 *buf, size_t len) |
| { |
| int attrlen, rta_len; |
| struct rtattr *attr; |
| |
| attrlen = len; |
| attr = (struct rtattr *) buf; |
| |
| rta_len = RTA_ALIGN(sizeof(struct rtattr)); |
| while (RTA_OK(attr, attrlen)) { |
| if (attr->rta_type == IFLA_IFNAME) { |
| if (os_strcmp(((char *) attr) + rta_len, |
| drv->first_bss->ifname) == 0) |
| return 1; |
| else |
| break; |
| } |
| attr = RTA_NEXT(attr, attrlen); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int wpa_driver_nl80211_own_ifindex(struct wpa_driver_nl80211_data *drv, |
| int ifindex, u8 *buf, size_t len) |
| { |
| if (drv->ifindex == ifindex) |
| return 1; |
| |
| if (drv->if_removed && wpa_driver_nl80211_own_ifname(drv, buf, len)) { |
| nl80211_check_global(drv->global); |
| wpa_printf(MSG_DEBUG, "nl80211: Update ifindex for a removed " |
| "interface"); |
| wpa_driver_nl80211_finish_drv_init(drv, NULL, 0, NULL); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static struct wpa_driver_nl80211_data * |
| nl80211_find_drv(struct nl80211_global *global, int idx, u8 *buf, size_t len) |
| { |
| struct wpa_driver_nl80211_data *drv; |
| dl_list_for_each(drv, &global->interfaces, |
| struct wpa_driver_nl80211_data, list) { |
| if (wpa_driver_nl80211_own_ifindex(drv, idx, buf, len) || |
| have_ifidx(drv, idx)) |
| return drv; |
| } |
| return NULL; |
| } |
| |
| |
| static void wpa_driver_nl80211_event_rtm_newlink(void *ctx, |
| struct ifinfomsg *ifi, |
| u8 *buf, size_t len) |
| { |
| struct nl80211_global *global = ctx; |
| struct wpa_driver_nl80211_data *drv; |
| int attrlen; |
| struct rtattr *attr; |
| u32 brid = 0; |
| char namebuf[IFNAMSIZ]; |
| char ifname[IFNAMSIZ + 1]; |
| char extra[100], *pos, *end; |
| |
| drv = nl80211_find_drv(global, ifi->ifi_index, buf, len); |
| if (!drv) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore RTM_NEWLINK event for foreign ifindex %d", |
| ifi->ifi_index); |
| return; |
| } |
| |
| extra[0] = '\0'; |
| pos = extra; |
| end = pos + sizeof(extra); |
| ifname[0] = '\0'; |
| |
| attrlen = len; |
| attr = (struct rtattr *) buf; |
| while (RTA_OK(attr, attrlen)) { |
| switch (attr->rta_type) { |
| case IFLA_IFNAME: |
| if (RTA_PAYLOAD(attr) >= IFNAMSIZ) |
| break; |
| os_memcpy(ifname, RTA_DATA(attr), RTA_PAYLOAD(attr)); |
| ifname[RTA_PAYLOAD(attr)] = '\0'; |
| break; |
| case IFLA_MASTER: |
| brid = nla_get_u32((struct nlattr *) attr); |
| pos += os_snprintf(pos, end - pos, " master=%u", brid); |
| break; |
| case IFLA_WIRELESS: |
| pos += os_snprintf(pos, end - pos, " wext"); |
| break; |
| case IFLA_OPERSTATE: |
| pos += os_snprintf(pos, end - pos, " operstate=%u", |
| nla_get_u32((struct nlattr *) attr)); |
| break; |
| case IFLA_LINKMODE: |
| pos += os_snprintf(pos, end - pos, " linkmode=%u", |
| nla_get_u32((struct nlattr *) attr)); |
| break; |
| } |
| attr = RTA_NEXT(attr, attrlen); |
| } |
| extra[sizeof(extra) - 1] = '\0'; |
| |
| wpa_printf(MSG_DEBUG, "RTM_NEWLINK: ifi_index=%d ifname=%s%s ifi_family=%d ifi_flags=0x%x (%s%s%s%s)", |
| ifi->ifi_index, ifname, extra, ifi->ifi_family, |
| ifi->ifi_flags, |
| (ifi->ifi_flags & IFF_UP) ? "[UP]" : "", |
| (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "", |
| (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "", |
| (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT]" : ""); |
| |
| if (!drv->if_disabled && !(ifi->ifi_flags & IFF_UP)) { |
| namebuf[0] = '\0'; |
| if (if_indextoname(ifi->ifi_index, namebuf) && |
| linux_iface_up(drv->global->ioctl_sock, namebuf) > 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore interface down " |
| "event since interface %s is up", namebuf); |
| drv->ignore_if_down_event = 0; |
| return; |
| } |
| wpa_printf(MSG_WARNING, "nl80211: Interface down (%s/%s)", |
| namebuf, ifname); |
| if (os_strcmp(drv->first_bss->ifname, ifname) != 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Not the main interface (%s) - do not indicate interface down", |
| drv->first_bss->ifname); |
| } else if (drv->ignore_if_down_event) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore interface down " |
| "event generated by mode change"); |
| drv->ignore_if_down_event = 0; |
| } else { |
| drv->if_disabled = 1; |
| wpa_supplicant_event(drv->ctx, |
| EVENT_INTERFACE_DISABLED, NULL); |
| |
| /* |
| * Try to get drv again, since it may be removed as |
| * part of the EVENT_INTERFACE_DISABLED handling for |
| * dynamic interfaces |
| */ |
| drv = nl80211_find_drv(global, ifi->ifi_index, |
| buf, len); |
| if (!drv) |
| return; |
| } |
| } |
| |
| if (drv->if_disabled && (ifi->ifi_flags & IFF_UP)) { |
| if (if_indextoname(ifi->ifi_index, namebuf) && |
| linux_iface_up(drv->global->ioctl_sock, namebuf) == 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore interface up " |
| "event since interface %s is down", |
| namebuf); |
| } else if (if_nametoindex(drv->first_bss->ifname) == 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore interface up " |
| "event since interface %s does not exist", |
| drv->first_bss->ifname); |
| } else if (drv->if_removed) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore interface up " |
| "event since interface %s is marked " |
| "removed", drv->first_bss->ifname); |
| } else { |
| struct i802_bss *bss; |
| u8 addr[ETH_ALEN]; |
| |
| /* Re-read MAC address as it may have changed */ |
| bss = get_bss_ifindex(drv, ifi->ifi_index); |
| if (bss && |
| linux_get_ifhwaddr(drv->global->ioctl_sock, |
| bss->ifname, addr) < 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: %s: failed to re-read MAC address", |
| bss->ifname); |
| } else if (bss && |
| os_memcmp(addr, bss->addr, ETH_ALEN) != 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Own MAC address on ifindex %d (%s) changed from " |
| MACSTR " to " MACSTR, |
| ifi->ifi_index, bss->ifname, |
| MAC2STR(bss->addr), |
| MAC2STR(addr)); |
| os_memcpy(bss->addr, addr, ETH_ALEN); |
| } |
| |
| wpa_printf(MSG_DEBUG, "nl80211: Interface up"); |
| drv->if_disabled = 0; |
| wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_ENABLED, |
| NULL); |
| } |
| } |
| |
| /* |
| * Some drivers send the association event before the operup event--in |
| * this case, lifting operstate in wpa_driver_nl80211_set_operstate() |
| * fails. This will hit us when wpa_supplicant does not need to do |
| * IEEE 802.1X authentication |
| */ |
| if (drv->operstate == 1 && |
| (ifi->ifi_flags & (IFF_LOWER_UP | IFF_DORMANT)) == IFF_LOWER_UP && |
| !(ifi->ifi_flags & IFF_RUNNING)) { |
| wpa_printf(MSG_DEBUG, "nl80211: Set IF_OPER_UP again based on ifi_flags and expected operstate"); |
| netlink_send_oper_ifla(drv->global->netlink, drv->ifindex, |
| -1, IF_OPER_UP); |
| } |
| |
| if (ifname[0]) |
| wpa_driver_nl80211_event_newlink(drv, ifname); |
| |
| if (ifi->ifi_family == AF_BRIDGE && brid) { |
| struct i802_bss *bss; |
| |
| /* device has been added to bridge */ |
| if (!if_indextoname(brid, namebuf)) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Could not find bridge ifname for ifindex %u", |
| brid); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "nl80211: Add ifindex %u for bridge %s", |
| brid, namebuf); |
| add_ifidx(drv, brid); |
| |
| for (bss = drv->first_bss; bss; bss = bss->next) { |
| if (os_strcmp(ifname, bss->ifname) == 0) { |
| os_strlcpy(bss->brname, namebuf, IFNAMSIZ); |
| break; |
| } |
| } |
| } |
| } |
| |
| |
| static void wpa_driver_nl80211_event_rtm_dellink(void *ctx, |
| struct ifinfomsg *ifi, |
| u8 *buf, size_t len) |
| { |
| struct nl80211_global *global = ctx; |
| struct wpa_driver_nl80211_data *drv; |
| int attrlen; |
| struct rtattr *attr; |
| u32 brid = 0; |
| char ifname[IFNAMSIZ + 1]; |
| char extra[100], *pos, *end; |
| |
| drv = nl80211_find_drv(global, ifi->ifi_index, buf, len); |
| if (!drv) { |
| wpa_printf(MSG_DEBUG, "nl80211: Ignore RTM_DELLINK event for foreign ifindex %d", |
| ifi->ifi_index); |
| return; |
| } |
| |
| extra[0] = '\0'; |
| pos = extra; |
| end = pos + sizeof(extra); |
| ifname[0] = '\0'; |
| |
| attrlen = len; |
| attr = (struct rtattr *) buf; |
| while (RTA_OK(attr, attrlen)) { |
| switch (attr->rta_type) { |
| case IFLA_IFNAME: |
| if (RTA_PAYLOAD(attr) >= IFNAMSIZ) |
| break; |
| os_memcpy(ifname, RTA_DATA(attr), RTA_PAYLOAD(attr)); |
| ifname[RTA_PAYLOAD(attr)] = '\0'; |
| break; |
| case IFLA_MASTER: |
| brid = nla_get_u32((struct nlattr *) attr); |
| pos += os_snprintf(pos, end - pos, " master=%u", brid); |
| break; |
| case IFLA_OPERSTATE: |
| pos += os_snprintf(pos, end - pos, " operstate=%u", |
| nla_get_u32((struct nlattr *) attr)); |
| break; |
| case IFLA_LINKMODE: |
| pos += os_snprintf(pos, end - pos, " linkmode=%u", |
| nla_get_u32((struct nlattr *) attr)); |
| break; |
| } |
| attr = RTA_NEXT(attr, attrlen); |
| } |
| extra[sizeof(extra) - 1] = '\0'; |
| |
| wpa_printf(MSG_DEBUG, "RTM_DELLINK: ifi_index=%d ifname=%s%s ifi_family=%d ifi_flags=0x%x (%s%s%s%s)", |
| ifi->ifi_index, ifname, extra, ifi->ifi_family, |
| ifi->ifi_flags, |
| (ifi->ifi_flags & IFF_UP) ? "[UP]" : "", |
| (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "", |
| (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "", |
| (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT]" : ""); |
| |
| if (ifname[0] && (ifi->ifi_family != AF_BRIDGE || !brid)) |
| wpa_driver_nl80211_event_dellink(drv, ifname); |
| |
| if (ifi->ifi_family == AF_BRIDGE && brid) { |
| /* device has been removed from bridge */ |
| char namebuf[IFNAMSIZ]; |
| |
| if (!if_indextoname(brid, namebuf)) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Could not find bridge ifname for ifindex %u", |
| brid); |
| } else { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Remove ifindex %u for bridge %s", |
| brid, namebuf); |
| } |
| del_ifidx(drv, brid); |
| } |
| } |
| |
| |
| unsigned int nl80211_get_assoc_freq(struct wpa_driver_nl80211_data *drv) |
| { |
| struct nl_msg *msg; |
| int ret; |
| struct nl80211_bss_info_arg arg; |
| |
| msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SCAN); |
| os_memset(&arg, 0, sizeof(arg)); |
| arg.drv = drv; |
| ret = send_and_recv_msgs(drv, msg, bss_info_handler, &arg); |
| if (ret == 0) { |
| unsigned int freq = drv->nlmode == NL80211_IFTYPE_ADHOC ? |
| arg.ibss_freq : arg.assoc_freq; |
| wpa_printf(MSG_DEBUG, "nl80211: Operating frequency for the " |
| "associated BSS from scan results: %u MHz", freq); |
| if (freq) |
| drv->assoc_freq = freq; |
| return drv->assoc_freq; |
| } |
| wpa_printf(MSG_DEBUG, "nl80211: Scan result fetch failed: ret=%d " |
| "(%s)", ret, strerror(-ret)); |
| return drv->assoc_freq; |
| } |
| |
| |
| static int get_link_signal(struct nl_msg *msg, void *arg) |
| { |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; |
| static struct nla_policy policy[NL80211_STA_INFO_MAX + 1] = { |
| [NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 }, |
| [NL80211_STA_INFO_SIGNAL_AVG] = { .type = NLA_U8 }, |
| [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = { .type = NLA_U8 }, |
| }; |
| struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1]; |
| static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = { |
| [NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 }, |
| [NL80211_RATE_INFO_MCS] = { .type = NLA_U8 }, |
| [NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG }, |
| [NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG }, |
| }; |
| struct wpa_signal_info *sig_change = arg; |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb[NL80211_ATTR_STA_INFO] || |
| nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, |
| tb[NL80211_ATTR_STA_INFO], policy)) |
| return NL_SKIP; |
| if (!sinfo[NL80211_STA_INFO_SIGNAL]) |
| return NL_SKIP; |
| |
| sig_change->current_signal = |
| (s8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]); |
| |
| if (sinfo[NL80211_STA_INFO_SIGNAL_AVG]) |
| sig_change->avg_signal = |
| (s8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL_AVG]); |
| else |
| sig_change->avg_signal = 0; |
| |
| if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]) |
| sig_change->avg_beacon_signal = |
| (s8) |
| nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]); |
| else |
| sig_change->avg_beacon_signal = 0; |
| |
| if (sinfo[NL80211_STA_INFO_TX_BITRATE]) { |
| if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX, |
| sinfo[NL80211_STA_INFO_TX_BITRATE], |
| rate_policy)) { |
| sig_change->current_txrate = 0; |
| } else { |
| if (rinfo[NL80211_RATE_INFO_BITRATE]) { |
| sig_change->current_txrate = |
| nla_get_u16(rinfo[ |
| NL80211_RATE_INFO_BITRATE]) * 100; |
| } |
| } |
| } |
| |
| return NL_SKIP; |
| } |
| |
| |
| int nl80211_get_link_signal(struct wpa_driver_nl80211_data *drv, |
| struct wpa_signal_info *sig) |
| { |
| struct nl_msg *msg; |
| |
| sig->current_signal = -9999; |
| sig->current_txrate = 0; |
| |
| if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_STATION)) || |
| nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, drv->bssid)) { |
| nlmsg_free(msg); |
| return -ENOBUFS; |
| } |
| |
| return send_and_recv_msgs(drv, msg, get_link_signal, sig); |
| } |
| |
| |
| static int get_link_noise(struct nl_msg *msg, void *arg) |
| { |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1]; |
| static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = { |
| [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 }, |
| [NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 }, |
| }; |
| struct wpa_signal_info *sig_change = arg; |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (!tb[NL80211_ATTR_SURVEY_INFO]) { |
| wpa_printf(MSG_DEBUG, "nl80211: survey data missing!"); |
| return NL_SKIP; |
| } |
| |
| if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX, |
| tb[NL80211_ATTR_SURVEY_INFO], |
| survey_policy)) { |
| wpa_printf(MSG_DEBUG, "nl80211: failed to parse nested " |
| "attributes!"); |
| return NL_SKIP; |
| } |
| |
| if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY]) |
| return NL_SKIP; |
| |
| if (nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]) != |
| sig_change->frequency) |
| return NL_SKIP; |
| |
| if (!sinfo[NL80211_SURVEY_INFO_NOISE]) |
| return NL_SKIP; |
| |
| sig_change->current_noise = |
| (s8) nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]); |
| |
| return NL_SKIP; |
| } |
| |
| |
| int nl80211_get_link_noise(struct wpa_driver_nl80211_data *drv, |
| struct wpa_signal_info *sig_change) |
| { |
| struct nl_msg *msg; |
| |
| sig_change->current_noise = 9999; |
| sig_change->frequency = drv->assoc_freq; |
| |
| msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SURVEY); |
| return send_and_recv_msgs(drv, msg, get_link_noise, sig_change); |
| } |
| |
| |
| static void wpa_driver_nl80211_event_receive(int sock, void *eloop_ctx, |
| void *handle) |
| { |
| struct nl_cb *cb = eloop_ctx; |
| int res; |
| |
| wpa_printf(MSG_MSGDUMP, "nl80211: Event message available"); |
| |
| res = nl_recvmsgs(handle, cb); |
| if (res < 0) { |
| wpa_printf(MSG_INFO, "nl80211: %s->nl_recvmsgs failed: %d", |
| __func__, res); |
| } |
| } |
| |
| |
| /** |
| * wpa_driver_nl80211_set_country - ask nl80211 to set the regulatory domain |
| * @priv: driver_nl80211 private data |
| * @alpha2_arg: country to which to switch to |
| * Returns: 0 on success, -1 on failure |
| * |
| * This asks nl80211 to set the regulatory domain for given |
| * country ISO / IEC alpha2. |
| */ |
| static int wpa_driver_nl80211_set_country(void *priv, const char *alpha2_arg) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| char alpha2[3]; |
| struct nl_msg *msg; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -ENOMEM; |
| |
| alpha2[0] = alpha2_arg[0]; |
| alpha2[1] = alpha2_arg[1]; |
| alpha2[2] = '\0'; |
| |
| if (!nl80211_cmd(drv, msg, 0, NL80211_CMD_REQ_SET_REG) || |
| nla_put_string(msg, NL80211_ATTR_REG_ALPHA2, alpha2)) { |
| nlmsg_free(msg); |
| return -EINVAL; |
| } |
| if (send_and_recv_msgs(drv, msg, NULL, NULL)) |
| return -EINVAL; |
| return 0; |
| } |
| |
| |
| static int nl80211_get_country(struct nl_msg *msg, void *arg) |
| { |
| char *alpha2 = arg; |
| struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| |
| nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb_msg[NL80211_ATTR_REG_ALPHA2]) { |
| wpa_printf(MSG_DEBUG, "nl80211: No country information available"); |
| return NL_SKIP; |
| } |
| os_strlcpy(alpha2, nla_data(tb_msg[NL80211_ATTR_REG_ALPHA2]), 3); |
| return NL_SKIP; |
| } |
| |
| |
| static int wpa_driver_nl80211_get_country(void *priv, char *alpha2) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -ENOMEM; |
| |
| nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_REG); |
| alpha2[0] = '\0'; |
| ret = send_and_recv_msgs(drv, msg, nl80211_get_country, alpha2); |
| if (!alpha2[0]) |
| ret = -1; |
| |
| return ret; |
| } |
| |
| |
| static int wpa_driver_nl80211_init_nl_global(struct nl80211_global *global) |
| { |
| int ret; |
| |
| global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (global->nl_cb == NULL) { |
| wpa_printf(MSG_ERROR, "nl80211: Failed to allocate netlink " |
| "callbacks"); |
| return -1; |
| } |
| |
| global->nl = nl_create_handle(global->nl_cb, "nl"); |
| if (global->nl == NULL) |
| goto err; |
| |
| global->nl80211_id = genl_ctrl_resolve(global->nl, "nl80211"); |
| if (global->nl80211_id < 0) { |
| wpa_printf(MSG_ERROR, "nl80211: 'nl80211' generic netlink not " |
| "found"); |
| goto err; |
| } |
| |
| global->nl_event = nl_create_handle(global->nl_cb, "event"); |
| if (global->nl_event == NULL) |
| goto err; |
| |
| ret = nl_get_multicast_id(global, "nl80211", "scan"); |
| if (ret >= 0) |
| ret = nl_socket_add_membership(global->nl_event, ret); |
| if (ret < 0) { |
| wpa_printf(MSG_ERROR, "nl80211: Could not add multicast " |
| "membership for scan events: %d (%s)", |
| ret, strerror(-ret)); |
| goto err; |
| } |
| |
| ret = nl_get_multicast_id(global, "nl80211", "mlme"); |
| if (ret >= 0) |
| ret = nl_socket_add_membership(global->nl_event, ret); |
| if (ret < 0) { |
| wpa_printf(MSG_ERROR, "nl80211: Could not add multicast " |
| "membership for mlme events: %d (%s)", |
| ret, strerror(-ret)); |
| goto err; |
| } |
| |
| ret = nl_get_multicast_id(global, "nl80211", "regulatory"); |
| if (ret >= 0) |
| ret = nl_socket_add_membership(global->nl_event, ret); |
| if (ret < 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Could not add multicast " |
| "membership for regulatory events: %d (%s)", |
| ret, strerror(-ret)); |
| /* Continue without regulatory events */ |
| } |
| |
| ret = nl_get_multicast_id(global, "nl80211", "vendor"); |
| if (ret >= 0) |
| ret = nl_socket_add_membership(global->nl_event, ret); |
| if (ret < 0) { |
| wpa_printf(MSG_DEBUG, "nl80211: Could not add multicast " |
| "membership for vendor events: %d (%s)", |
| ret, strerror(-ret)); |
| /* Continue without vendor events */ |
| } |
| |
| nl_cb_set(global->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, |
| no_seq_check, NULL); |
| nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, |
| process_global_event, global); |
| |
| nl80211_register_eloop_read(&global->nl_event, |
| wpa_driver_nl80211_event_receive, |
| global->nl_cb); |
| |
| return 0; |
| |
| err: |
| nl_destroy_handles(&global->nl_event); |
| nl_destroy_handles(&global->nl); |
| nl_cb_put(global->nl_cb); |
| global->nl_cb = NULL; |
| return -1; |
| } |
| |
| |
| static void nl80211_check_global(struct nl80211_global *global) |
| { |
| struct nl_handle *handle; |
| const char *groups[] = { "scan", "mlme", "regulatory", "vendor", NULL }; |
| int ret; |
| unsigned int i; |
| |
| /* |
| * Try to re-add memberships to handle case of cfg80211 getting reloaded |
| * and all registration having been cleared. |
| */ |
| handle = (void *) (((intptr_t) global->nl_event) ^ |
| ELOOP_SOCKET_INVALID); |
| |
| for (i = 0; groups[i]; i++) { |
| ret = nl_get_multicast_id(global, "nl80211", groups[i]); |
| if (ret >= 0) |
| ret = nl_socket_add_membership(handle, ret); |
| if (ret < 0) { |
| wpa_printf(MSG_INFO, |
| "nl80211: Could not re-add multicast membership for %s events: %d (%s)", |
| groups[i], ret, strerror(-ret)); |
| } |
| } |
| } |
| |
| |
| static void wpa_driver_nl80211_rfkill_blocked(void *ctx) |
| { |
| wpa_printf(MSG_DEBUG, "nl80211: RFKILL blocked"); |
| /* |
| * This may be for any interface; use ifdown event to disable |
| * interface. |
| */ |
| } |
| |
| |
| static void wpa_driver_nl80211_rfkill_unblocked(void *ctx) |
| { |
| struct wpa_driver_nl80211_data *drv = ctx; |
| wpa_printf(MSG_DEBUG, "nl80211: RFKILL unblocked"); |
| if (i802_set_iface_flags(drv->first_bss, 1)) { |
| wpa_printf(MSG_DEBUG, "nl80211: Could not set interface UP " |
| "after rfkill unblock"); |
| return; |
| } |
| /* rtnetlink ifup handler will report interface as enabled */ |
| } |
| |
| |
| static void wpa_driver_nl80211_handle_eapol_tx_status(int sock, |
| void *eloop_ctx, |
| void *handle) |
| { |
| struct wpa_driver_nl80211_data *drv = eloop_ctx; |
| u8 data[2048]; |
| struct msghdr msg; |
| struct iovec entry; |
| u8 control[512]; |
| struct cmsghdr *cmsg; |
| int res, found_ee = 0, found_wifi = 0, acked = 0; |
| union wpa_event_data event; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = &entry; |
| msg.msg_iovlen = 1; |
| entry.iov_base = data; |
| entry.iov_len = sizeof(data); |
| msg.msg_control = &control; |
| msg.msg_controllen = sizeof(control); |
| |
| res = recvmsg(sock, &msg, MSG_ERRQUEUE); |
| /* if error or not fitting 802.3 header, return */ |
| if (res < 14) |
| return; |
| |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) |
| { |
| if (cmsg->cmsg_level == SOL_SOCKET && |
| cmsg->cmsg_type == SCM_WIFI_STATUS) { |
| int *ack; |
| |
| found_wifi = 1; |
| ack = (void *)CMSG_DATA(cmsg); |
| acked = *ack; |
| } |
| |
| if (cmsg->cmsg_level == SOL_PACKET && |
| cmsg->cmsg_type == PACKET_TX_TIMESTAMP) { |
| struct sock_extended_err *err = |
| (struct sock_extended_err *)CMSG_DATA(cmsg); |
| |
| if (err->ee_origin == SO_EE_ORIGIN_TXSTATUS) |
| found_ee = 1; |
| } |
| } |
| |
| if (!found_ee || !found_wifi) |
| return; |
| |
| memset(&event, 0, sizeof(event)); |
| event.eapol_tx_status.dst = data; |
| event.eapol_tx_status.data = data + 14; |
| event.eapol_tx_status.data_len = res - 14; |
| event.eapol_tx_status.ack = acked; |
| wpa_supplicant_event(drv->ctx, EVENT_EAPOL_TX_STATUS, &event); |
| } |
| |
| |
| static int nl80211_init_bss(struct i802_bss *bss) |
| { |
| bss->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!bss->nl_cb) |
| return -1; |
| |
| nl_cb_set(bss->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, |
| no_seq_check, NULL); |
| nl_cb_set(bss->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, |
| process_bss_event, bss); |
| |
| return 0; |
| } |
| |
| |
| static void nl80211_destroy_bss(struct i802_bss *bss) |
| { |
| nl_cb_put(bss->nl_cb); |
| bss->nl_cb = NULL; |
| } |
| |
| |
| static void * wpa_driver_nl80211_drv_init(void *ctx, const char *ifname, |
| void *global_priv, int hostapd, |
| const u8 *set_addr, |
| const char *driver_params) |
| { |
| struct wpa_driver_nl80211_data *drv; |
| struct rfkill_config *rcfg; |
| struct i802_bss *bss; |
| |
| if (global_priv == NULL) |
| return NULL; |
| drv = os_zalloc(sizeof(*drv)); |
| if (drv == NULL) |
| return NULL; |
| drv->global = global_priv; |
| drv->ctx = ctx; |
| drv->hostapd = !!hostapd; |
| drv->eapol_sock = -1; |
| |
| /* |
| * There is no driver capability flag for this, so assume it is |
| * supported and disable this on first attempt to use if the driver |
| * rejects the command due to missing support. |
| */ |
| drv->set_rekey_offload = 1; |
| |
| drv->num_if_indices = sizeof(drv->default_if_indices) / sizeof(int); |
| drv->if_indices = drv->default_if_indices; |
| |
| drv->first_bss = os_zalloc(sizeof(*drv->first_bss)); |
| if (!drv->first_bss) { |
| os_free(drv); |
| return NULL; |
| } |
| bss = drv->first_bss; |
| bss->drv = drv; |
| bss->ctx = ctx; |
| |
| os_strlcpy(bss->ifname, ifname, sizeof(bss->ifname)); |
| drv->monitor_ifidx = -1; |
| drv->monitor_sock = -1; |
| drv->eapol_tx_sock = -1; |
| drv->ap_scan_as_station = NL80211_IFTYPE_UNSPECIFIED; |
| |
| if (nl80211_init_bss(bss)) |
| goto failed; |
| |
| rcfg = os_zalloc(sizeof(*rcfg)); |
| if (rcfg == NULL) |
| goto failed; |
| rcfg->ctx = drv; |
| os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname)); |
| rcfg->blocked_cb = wpa_driver_nl80211_rfkill_blocked; |
| rcfg->unblocked_cb = wpa_driver_nl80211_rfkill_unblocked; |
| drv->rfkill = rfkill_init(rcfg); |
| if (drv->rfkill == NULL) { |
| wpa_printf(MSG_DEBUG, "nl80211: RFKILL status not available"); |
| os_free(rcfg); |
| } |
| |
| if (linux_iface_up(drv->global->ioctl_sock, ifname) > 0) |
| drv->start_iface_up = 1; |
| |
| if (wpa_driver_nl80211_finish_drv_init(drv, set_addr, 1, driver_params)) |
| goto failed; |
| |
| drv->eapol_tx_sock = socket(PF_PACKET, SOCK_DGRAM, 0); |
| if (drv->eapol_tx_sock < 0) |
| goto failed; |
| |
| if (drv->data_tx_status) { |
| int enabled = 1; |
| |
| if (setsockopt(drv->eapol_tx_sock, SOL_SOCKET, SO_WIFI_STATUS, |
| &enabled, sizeof(enabled)) < 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: wifi status sockopt failed\n"); |
| drv->data_tx_status = 0; |
| if (!drv->use_monitor) |
| drv->capa.flags &= |
| ~WPA_DRIVER_FLAGS_EAPOL_TX_STATUS; |
| } else { |
| eloop_register_read_sock(drv->eapol_tx_sock, |
| wpa_driver_nl80211_handle_eapol_tx_status, |
| drv, NULL); |
| } |
| } |
| |
| if (drv->global) { |
| nl80211_check_global(drv->global); |
| dl_list_add(&drv->global->interfaces, &drv->list); |
| drv->in_interface_list = 1; |
| } |
| |
| return bss; |
| |
| failed: |
| wpa_driver_nl80211_deinit(bss); |
| return NULL; |
| } |
| |
| |
| /** |
| * wpa_driver_nl80211_init - Initialize nl80211 driver interface |
| * @ctx: context to be used when calling wpa_supplicant functions, |
| * e.g., wpa_supplicant_event() |
| * @ifname: interface name, e.g., wlan0 |
| * @global_priv: private driver global data from global_init() |
| * Returns: Pointer to private data, %NULL on failure |
| */ |
| static void * wpa_driver_nl80211_init(void *ctx, const char *ifname, |
| void *global_priv) |
| { |
| return wpa_driver_nl80211_drv_init(ctx, ifname, global_priv, 0, NULL, |
| NULL); |
| } |
| |
| |
| static int nl80211_register_frame(struct i802_bss *bss, |
| struct nl_handle *nl_handle, |
| u16 type, const u8 *match, size_t match_len) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| struct nl_msg *msg; |
| int ret; |
| char buf[30]; |
| |
| buf[0] = '\0'; |
| wpa_snprintf_hex(buf, sizeof(buf), match, match_len); |
| wpa_printf(MSG_DEBUG, "nl80211: Register frame type=0x%x (%s) nl_handle=%p match=%s", |
| type, fc2str(type), nl_handle, buf); |
| |
| if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_REGISTER_ACTION)) || |
| nla_put_u16(msg, NL80211_ATTR_FRAME_TYPE, type) || |
| nla_put(msg, NL80211_ATTR_FRAME_MATCH, match_len, match)) { |
| nlmsg_free(msg); |
| return -1; |
| } |
| |
| ret = send_and_recv(drv->global, nl_handle, msg, NULL, NULL); |
| if (ret) { |
| wpa_printf(MSG_DEBUG, "nl80211: Register frame command " |
| "failed (type=%u): ret=%d (%s)", |
| type, ret, strerror(-ret)); |
| wpa_hexdump(MSG_DEBUG, "nl80211: Register frame match", |
| match, match_len); |
| } |
| return ret; |
| } |
| |
| |
| static int nl80211_alloc_mgmt_handle(struct i802_bss *bss) |
| { |
| if (bss->nl_mgmt) { |
| wpa_printf(MSG_DEBUG, "nl80211: Mgmt reporting " |
| "already on! (nl_mgmt=%p)", bss->nl_mgmt); |
| return -1; |
| } |
| |
| bss->nl_mgmt = nl_create_handle(bss->nl_cb, "mgmt"); |
| if (bss->nl_mgmt == NULL) |
| return -1; |
| |
| return 0; |
| } |
| |
| |
| static void nl80211_mgmt_handle_register_eloop(struct i802_bss *bss) |
| { |
| nl80211_register_eloop_read(&bss->nl_mgmt, |
| wpa_driver_nl80211_event_receive, |
| bss->nl_cb); |
| } |
| |
| |
| static int nl80211_register_action_frame(struct i802_bss *bss, |
| const u8 *match, size_t match_len) |
| { |
| u16 type = (WLAN_FC_TYPE_MGMT << 2) | (WLAN_FC_STYPE_ACTION << 4); |
| return nl80211_register_frame(bss, bss->nl_mgmt, |
| type, match, match_len); |
| } |
| |
| |
| static int nl80211_mgmt_subscribe_non_ap(struct i802_bss *bss) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| int ret = 0; |
| |
| if (nl80211_alloc_mgmt_handle(bss)) |
| return -1; |
| wpa_printf(MSG_DEBUG, "nl80211: Subscribe to mgmt frames with non-AP " |
| "handle %p", bss->nl_mgmt); |
| |
| if (drv->nlmode == NL80211_IFTYPE_ADHOC) { |
| u16 type = (WLAN_FC_TYPE_MGMT << 2) | (WLAN_FC_STYPE_AUTH << 4); |
| |
| /* register for any AUTH message */ |
| nl80211_register_frame(bss, bss->nl_mgmt, type, NULL, 0); |
| } |
| |
| #ifdef CONFIG_INTERWORKING |
| /* QoS Map Configure */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x01\x04", 2) < 0) |
| ret = -1; |
| #endif /* CONFIG_INTERWORKING */ |
| #if defined(CONFIG_P2P) || defined(CONFIG_INTERWORKING) |
| /* GAS Initial Request */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x04\x0a", 2) < 0) |
| ret = -1; |
| /* GAS Initial Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x04\x0b", 2) < 0) |
| ret = -1; |
| /* GAS Comeback Request */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x04\x0c", 2) < 0) |
| ret = -1; |
| /* GAS Comeback Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x04\x0d", 2) < 0) |
| ret = -1; |
| /* Protected GAS Initial Request */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x09\x0a", 2) < 0) |
| ret = -1; |
| /* Protected GAS Initial Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x09\x0b", 2) < 0) |
| ret = -1; |
| /* Protected GAS Comeback Request */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x09\x0c", 2) < 0) |
| ret = -1; |
| /* Protected GAS Comeback Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x09\x0d", 2) < 0) |
| ret = -1; |
| #endif /* CONFIG_P2P || CONFIG_INTERWORKING */ |
| #ifdef CONFIG_P2P |
| /* P2P Public Action */ |
| if (nl80211_register_action_frame(bss, |
| (u8 *) "\x04\x09\x50\x6f\x9a\x09", |
| 6) < 0) |
| ret = -1; |
| /* P2P Action */ |
| if (nl80211_register_action_frame(bss, |
| (u8 *) "\x7f\x50\x6f\x9a\x09", |
| 5) < 0) |
| ret = -1; |
| #endif /* CONFIG_P2P */ |
| #ifdef CONFIG_IEEE80211W |
| /* SA Query Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x08\x01", 2) < 0) |
| ret = -1; |
| #endif /* CONFIG_IEEE80211W */ |
| #ifdef CONFIG_TDLS |
| if ((drv->capa.flags & WPA_DRIVER_FLAGS_TDLS_SUPPORT)) { |
| /* TDLS Discovery Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x04\x0e", 2) < |
| 0) |
| ret = -1; |
| } |
| #endif /* CONFIG_TDLS */ |
| |
| /* FT Action frames */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x06", 1) < 0) |
| ret = -1; |
| else |
| drv->capa.key_mgmt |= WPA_DRIVER_CAPA_KEY_MGMT_FT | |
| WPA_DRIVER_CAPA_KEY_MGMT_FT_PSK; |
| |
| /* WNM - BSS Transition Management Request */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x07", 2) < 0) |
| ret = -1; |
| /* WNM-Sleep Mode Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x11", 2) < 0) |
| ret = -1; |
| |
| #ifdef CONFIG_HS20 |
| /* WNM-Notification */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x1a", 2) < 0) |
| ret = -1; |
| #endif /* CONFIG_HS20 */ |
| |
| /* WMM-AC ADDTS Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x11\x01", 2) < 0) |
| ret = -1; |
| |
| /* WMM-AC DELTS */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x11\x02", 2) < 0) |
| ret = -1; |
| |
| /* Radio Measurement - Neighbor Report Response */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x05\x05", 2) < 0) |
| ret = -1; |
| |
| /* Radio Measurement - Link Measurement Request */ |
| if ((drv->capa.rrm_flags & WPA_DRIVER_FLAGS_TX_POWER_INSERTION) && |
| (nl80211_register_action_frame(bss, (u8 *) "\x05\x02", 2) < 0)) |
| ret = -1; |
| |
| nl80211_mgmt_handle_register_eloop(bss); |
| |
| return ret; |
| } |
| |
| |
| static int nl80211_mgmt_subscribe_mesh(struct i802_bss *bss) |
| { |
| int ret = 0; |
| |
| if (nl80211_alloc_mgmt_handle(bss)) |
| return -1; |
| |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Subscribe to mgmt frames with mesh handle %p", |
| bss->nl_mgmt); |
| |
| /* Auth frames for mesh SAE */ |
| if (nl80211_register_frame(bss, bss->nl_mgmt, |
| (WLAN_FC_TYPE_MGMT << 2) | |
| (WLAN_FC_STYPE_AUTH << 4), |
| NULL, 0) < 0) |
| ret = -1; |
| |
| /* Mesh peering open */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0f\x01", 2) < 0) |
| ret = -1; |
| /* Mesh peering confirm */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0f\x02", 2) < 0) |
| ret = -1; |
| /* Mesh peering close */ |
| if (nl80211_register_action_frame(bss, (u8 *) "\x0f\x03", 2) < 0) |
| ret = -1; |
| |
| nl80211_mgmt_handle_register_eloop(bss); |
| |
| return ret; |
| } |
| |
| |
| static int nl80211_register_spurious_class3(struct i802_bss *bss) |
| { |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nl80211_bss_msg(bss, 0, NL80211_CMD_UNEXPECTED_FRAME); |
| ret = send_and_recv(bss->drv->global, bss->nl_mgmt, msg, NULL, NULL); |
| if (ret) { |
| wpa_printf(MSG_DEBUG, "nl80211: Register spurious class3 " |
| "failed: ret=%d (%s)", |
| ret, strerror(-ret)); |
| } |
| return ret; |
| } |
| |
| |
| static int nl80211_mgmt_subscribe_ap(struct i802_bss *bss) |
| { |
| static const int stypes[] = { |
| WLAN_FC_STYPE_AUTH, |
| WLAN_FC_STYPE_ASSOC_REQ, |
| WLAN_FC_STYPE_REASSOC_REQ, |
| WLAN_FC_STYPE_DISASSOC, |
| WLAN_FC_STYPE_DEAUTH, |
| WLAN_FC_STYPE_ACTION, |
| WLAN_FC_STYPE_PROBE_REQ, |
| /* Beacon doesn't work as mac80211 doesn't currently allow |
| * it, but it wouldn't really be the right thing anyway as |
| * it isn't per interface ... maybe just dump the scan |
| * results periodically for OLBC? |
| */ |
| /* WLAN_FC_STYPE_BEACON, */ |
| }; |
| unsigned int i; |
| |
| if (nl80211_alloc_mgmt_handle(bss)) |
| return -1; |
| wpa_printf(MSG_DEBUG, "nl80211: Subscribe to mgmt frames with AP " |
| "handle %p", bss->nl_mgmt); |
| |
| for (i = 0; i < ARRAY_SIZE(stypes); i++) { |
| if (nl80211_register_frame(bss, bss->nl_mgmt, |
| (WLAN_FC_TYPE_MGMT << 2) | |
| (stypes[i] << 4), |
| NULL, 0) < 0) { |
| goto out_err; |
| } |
| } |
| |
| if (nl80211_register_spurious_class3(bss)) |
| goto out_err; |
| |
| if (nl80211_get_wiphy_data_ap(bss) == NULL) |
| goto out_err; |
| |
| nl80211_mgmt_handle_register_eloop(bss); |
| return 0; |
| |
| out_err: |
| nl_destroy_handles(&bss->nl_mgmt); |
| return -1; |
| } |
| |
| |
| static int nl80211_mgmt_subscribe_ap_dev_sme(struct i802_bss *bss) |
| { |
| if (nl80211_alloc_mgmt_handle(bss)) |
| return -1; |
| wpa_printf(MSG_DEBUG, "nl80211: Subscribe to mgmt frames with AP " |
| "handle %p (device SME)", bss->nl_mgmt); |
| |
| if (nl80211_register_frame(bss, bss->nl_mgmt, |
| (WLAN_FC_TYPE_MGMT << 2) | |
| (WLAN_FC_STYPE_ACTION << 4), |
| NULL, 0) < 0) |
| goto out_err; |
| |
| nl80211_mgmt_handle_register_eloop(bss); |
| return 0; |
| |
| out_err: |
| nl_destroy_handles(&bss->nl_mgmt); |
| return -1; |
| } |
| |
| |
| static void nl80211_mgmt_unsubscribe(struct i802_bss *bss, const char *reason) |
| { |
| if (bss->nl_mgmt == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "nl80211: Unsubscribe mgmt frames handle %p " |
| "(%s)", bss->nl_mgmt, reason); |
| nl80211_destroy_eloop_handle(&bss->nl_mgmt); |
| |
| nl80211_put_wiphy_data_ap(bss); |
| } |
| |
| |
| static void wpa_driver_nl80211_send_rfkill(void *eloop_ctx, void *timeout_ctx) |
| { |
| wpa_supplicant_event(timeout_ctx, EVENT_INTERFACE_DISABLED, NULL); |
| } |
| |
| |
| static void nl80211_del_p2pdev(struct i802_bss *bss) |
| { |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_DEL_INTERFACE); |
| ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL); |
| |
| wpa_printf(MSG_DEBUG, "nl80211: Delete P2P Device %s (0x%llx): %s", |
| bss->ifname, (long long unsigned int) bss->wdev_id, |
| strerror(-ret)); |
| } |
| |
| |
| static int nl80211_set_p2pdev(struct i802_bss *bss, int start) |
| { |
| struct nl_msg *msg; |
| int ret; |
| |
| msg = nl80211_cmd_msg(bss, 0, start ? NL80211_CMD_START_P2P_DEVICE : |
| NL80211_CMD_STOP_P2P_DEVICE); |
| ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL); |
| |
| wpa_printf(MSG_DEBUG, "nl80211: %s P2P Device %s (0x%llx): %s", |
| start ? "Start" : "Stop", |
| bss->ifname, (long long unsigned int) bss->wdev_id, |
| strerror(-ret)); |
| return ret; |
| } |
| |
| |
| static int i802_set_iface_flags(struct i802_bss *bss, int up) |
| { |
| enum nl80211_iftype nlmode; |
| |
| nlmode = nl80211_get_ifmode(bss); |
| if (nlmode != NL80211_IFTYPE_P2P_DEVICE) { |
| return linux_set_iface_flags(bss->drv->global->ioctl_sock, |
| bss->ifname, up); |
| } |
| |
| /* P2P Device has start/stop which is equivalent */ |
| return nl80211_set_p2pdev(bss, up); |
| } |
| |
| |
| #ifdef CONFIG_TESTING_OPTIONS |
| static int qca_vendor_test_cmd_handler(struct nl_msg *msg, void *arg) |
| { |
| /* struct wpa_driver_nl80211_data *drv = arg; */ |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| |
| |
| wpa_printf(MSG_DEBUG, |
| "nl80211: QCA vendor test command response received"); |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb[NL80211_ATTR_VENDOR_DATA]) { |
| wpa_printf(MSG_DEBUG, "nl80211: No vendor data attribute"); |
| return NL_SKIP; |
| } |
| |
| wpa_hexdump(MSG_DEBUG, |
| "nl80211: Received QCA vendor test command response", |
| nla_data(tb[NL80211_ATTR_VENDOR_DATA]), |
| nla_len(tb[NL80211_ATTR_VENDOR_DATA])); |
| |
| return NL_SKIP; |
| } |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| |
| |
| static void qca_vendor_test(struct wpa_driver_nl80211_data *drv) |
| { |
| #ifdef CONFIG_TESTING_OPTIONS |
| struct nl_msg *msg; |
| struct nlattr *params; |
| int ret; |
| |
| if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) || |
| nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA) || |
| nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, |
| QCA_NL80211_VENDOR_SUBCMD_TEST) || |
| !(params = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) || |
| nla_put_u32(msg, QCA_WLAN_VENDOR_ATTR_TEST, 123)) { |
| nlmsg_free(msg); |
| return; |
| } |
| nla_nest_end(msg, params); |
| |
| ret = send_and_recv_msgs(drv, msg, qca_vendor_test_cmd_handler, drv); |
| wpa_printf(MSG_DEBUG, |
| "nl80211: QCA vendor test command returned %d (%s)", |
| ret, strerror(-ret)); |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| } |
| |
| |
| static int |
| wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv, |
| const u8 *set_addr, int first, |
| const char *driver_params) |
| { |
| struct i802_bss *bss = drv->first_bss; |
| int send_rfkill_event = 0; |
| enum nl80211_iftype nlmode; |
| |
| drv->ifindex = if_nametoindex(bss->ifname); |
| bss->ifindex = drv->ifindex; |
| bss->wdev_id = drv->global->if_add_wdevid; |
| bss->wdev_id_set = drv->global->if_add_wdevid_set; |
| |
| bss->if_dynamic = drv->ifindex == drv->global->if_add_ifindex; |
| bss->if_dynamic = bss->if_dynamic || drv->global->if_add_wdevid_set; |
| drv->global->if_add_wdevid_set = 0; |
| |
| if (!bss->if_dynamic && nl80211_get_ifmode(bss) == NL80211_IFTYPE_AP) |
| bss->static_ap = 1; |
| |
| if (wpa_driver_nl80211_capa(drv)) |
| return -1; |
| |
| if (driver_params && nl80211_set_param(bss, driver_params) < 0) |
| return -1; |
| |
| wpa_printf(MSG_DEBUG, "nl80211: interface %s in phy %s", |
| bss->ifname, drv->phyname); |
| |
| if (set_addr && |
| (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 0) || |
| linux_set_ifhwaddr(drv->global->ioctl_sock, bss->ifname, |
| set_addr))) |
| return -1; |
| |
| if (first && nl80211_get_ifmode(bss) == NL80211_IFTYPE_AP) |
| drv->start_mode_ap = 1; |
| |
| if (drv->hostapd || bss->static_ap) |
| nlmode = NL80211_IFTYPE_AP; |
| else if (bss->if_dynamic) |
| nlmode = nl80211_get_ifmode(bss); |
| else |
| nlmode = NL80211_IFTYPE_STATION; |
| |
| if (wpa_driver_nl80211_set_mode(bss, nlmode) < 0) { |
| wpa_printf(MSG_ERROR, "nl80211: Could not configure driver mode"); |
| return -1; |
| } |
| |
| if (nlmode == NL80211_IFTYPE_P2P_DEVICE) |
| nl80211_get_macaddr(bss); |
| |
| if (!rfkill_is_blocked(drv->rfkill)) { |
| int ret = i802_set_iface_flags(bss, 1); |
| if (ret) { |
| wpa_printf(MSG_ERROR, "nl80211: Could not set " |
| "interface '%s' UP", bss->ifname); |
| return ret; |
| } |
| if (nlmode == NL80211_IFTYPE_P2P_DEVICE) |
| return ret; |
| } else { |
| wpa_printf(MSG_DEBUG, "nl80211: Could not yet enable " |
| "interface '%s' due to rfkill", bss->ifname); |
| if (nlmode == NL80211_IFTYPE_P2P_DEVICE) |
| return 0; |
| drv->if_disabled = 1; |
| send_rfkill_event = 1; |
| } |
| |
| if (!drv->hostapd) |
| netlink_send_oper_ifla(drv->global->netlink, drv->ifindex, |
| 1, IF_OPER_DORMANT); |
| |
| if (linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname, |
| bss->addr)) |
| return -1; |
| os_memcpy(drv->perm_addr, bss->addr, ETH_ALEN); |
| |
| if (send_rfkill_event) { |
| eloop_register_timeout(0, 0, wpa_driver_nl80211_send_rfkill, |
| drv, drv->ctx); |
| } |
| |
| if (drv->vendor_cmd_test_avail) |
| qca_vendor_test(drv); |
| |
| return 0; |
| } |
| |
| |
| static int wpa_driver_nl80211_del_beacon(struct wpa_driver_nl80211_data *drv) |
| { |
| struct nl_msg *msg; |
| |
| wpa_printf(MSG_DEBUG, "nl80211: Remove beacon (ifindex=%d)", |
| drv->ifindex); |
| msg = nl80211_drv_msg(drv, 0, NL80211_CMD_DEL_BEACON); |
| return send_and_recv_msgs(drv, msg, NULL, NULL); |
| } |
| |
| |
| /** |
| * wpa_driver_nl80211_deinit - Deinitialize nl80211 driver interface |
| * @bss: Pointer to private nl80211 data from wpa_driver_nl80211_init() |
| * |
| * Shut down driver interface and processing of driver events. Free |
| * private data buffer if one was allocated in wpa_driver_nl80211_init(). |
| */ |
| static void wpa_driver_nl80211_deinit(struct i802_bss *bss) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| |
| wpa_printf(MSG_INFO, "nl80211: deinit ifname=%s disabled_11b_rates=%d", |
| bss->ifname, drv->disabled_11b_rates); |
| |
| bss->in_deinit = 1; |
| if (drv->data_tx_status) |
| eloop_unregister_read_sock(drv->eapol_tx_sock); |
| if (drv->eapol_tx_sock >= 0) |
| close(drv->eapol_tx_sock); |
| |
| if (bss->nl_preq) |
| wpa_driver_nl80211_probe_req_report(bss, 0); |
| if (bss->added_if_into_bridge) { |
| if (linux_br_del_if(drv->global->ioctl_sock, bss->brname, |
| bss->ifname) < 0) |
| wpa_printf(MSG_INFO, "nl80211: Failed to remove " |
| "interface %s from bridge %s: %s", |
| bss->ifname, bss->brname, strerror(errno)); |
| if (drv->rtnl_sk) |
| nl80211_handle_destroy(drv->rtnl_sk); |
| } |
| if (bss->added_bridge) { |
| if (linux_set_iface_flags(drv->global->ioctl_sock, bss->brname, |
| 0) < 0) |
| wpa_printf(MSG_INFO, |
| "nl80211: Could not set bridge %s down", |
| bss->brname); |
| if (linux_br_del(drv->global->ioctl_sock, bss->brname) < 0) |
| wpa_printf(MSG_INFO, "nl80211: Failed to remove " |
| "bridge %s: %s", |
| bss->brname, strerror(errno)); |
| } |
| |
| nl80211_remove_monitor_interface(drv); |
| |
| if (is_ap_interface(drv->nlmode)) |
| wpa_driver_nl80211_del_beacon(drv); |
| |
| if (drv->eapol_sock >= 0) { |
| eloop_unregister_read_sock(drv->eapol_sock); |
| close(drv->eapol_sock); |
| } |
| |
| if (drv->if_indices != drv->default_if_indices) |
| os_free(drv->if_indices); |
| |
| if (drv->disabled_11b_rates) |
| nl80211_disable_11b_rates(drv, drv->ifindex, 0); |
| |
| netlink_send_oper_ifla(drv->global->netlink, drv->ifindex, 0, |
| IF_OPER_UP); |
| eloop_cancel_timeout(wpa_driver_nl80211_send_rfkill, drv, drv->ctx); |
| rfkill_deinit(drv->rfkill); |
| |
| eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx); |
| |
| if (!drv->start_iface_up) |
| (void) i802_set_iface_flags(bss, 0); |
| |
| if (drv->addr_changed) { |
| if (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, |
| 0) < 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Could not set interface down to restore permanent MAC address"); |
| } |
| if (linux_set_ifhwaddr(drv->global->ioctl_sock, bss->ifname, |
| drv->perm_addr) < 0) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Could not restore permanent MAC address"); |
| } |
| } |
| |
| if (drv->nlmode != NL80211_IFTYPE_P2P_DEVICE) { |
| if (!drv->hostapd || !drv->start_mode_ap) |
| wpa_driver_nl80211_set_mode(bss, |
| NL80211_IFTYPE_STATION); |
| nl80211_mgmt_unsubscribe(bss, "deinit"); |
| } else { |
| nl80211_mgmt_unsubscribe(bss, "deinit"); |
| nl80211_del_p2pdev(bss); |
| } |
| |
| nl80211_destroy_bss(drv->first_bss); |
| |
| os_free(drv->filter_ssids); |
| |
| os_free(drv->auth_ie); |
| |
| if (drv->in_interface_list) |
| dl_list_del(&drv->list); |
| |
| os_free(drv->extended_capa); |
| os_free(drv->extended_capa_mask); |
| os_free(drv->first_bss); |
| os_free(drv); |
| } |
| |
| |
| static u32 wpa_alg_to_cipher_suite(enum wpa_alg alg, size_t key_len) |
| { |
| switch (alg) { |
| case WPA_ALG_WEP: |
| if (key_len == 5) |
| return WLAN_CIPHER_SUITE_WEP40; |
| return WLAN_CIPHER_SUITE_WEP104; |
| case WPA_ALG_TKIP: |
| return WLAN_CIPHER_SUITE_TKIP; |
| case WPA_ALG_CCMP: |
| return WLAN_CIPHER_SUITE_CCMP; |
| case WPA_ALG_GCMP: |
| return WLAN_CIPHER_SUITE_GCMP; |
| case WPA_ALG_CCMP_256: |
| return WLAN_CIPHER_SUITE_CCMP_256; |
| case WPA_ALG_GCMP_256: |
| return WLAN_CIPHER_SUITE_GCMP_256; |
| case WPA_ALG_IGTK: |
| return WLAN_CIPHER_SUITE_AES_CMAC; |
| case WPA_ALG_BIP_GMAC_128: |
| return WLAN_CIPHER_SUITE_BIP_GMAC_128; |
| case WPA_ALG_BIP_GMAC_256: |
| return WLAN_CIPHER_SUITE_BIP_GMAC_256; |
| case WPA_ALG_BIP_CMAC_256: |
| return WLAN_CIPHER_SUITE_BIP_CMAC_256; |
| case WPA_ALG_SMS4: |
| return WLAN_CIPHER_SUITE_SMS4; |
| case WPA_ALG_KRK: |
| return WLAN_CIPHER_SUITE_KRK; |
| case WPA_ALG_NONE: |
| case WPA_ALG_PMK: |
| wpa_printf(MSG_ERROR, "nl80211: Unexpected encryption algorithm %d", |
| alg); |
| return 0; |
| } |
| |
| wpa_printf(MSG_ERROR, "nl80211: Unsupported encryption algorithm %d", |
| alg); |
| return 0; |
| } |
| |
| |
| static u32 wpa_cipher_to_cipher_suite(unsigned int cipher) |
| { |
| switch (cipher) { |
| case WPA_CIPHER_CCMP_256: |
| return WLAN_CIPHER_SUITE_CCMP_256; |
| case WPA_CIPHER_GCMP_256: |
| return WLAN_CIPHER_SUITE_GCMP_256; |
| case WPA_CIPHER_CCMP: |
| return WLAN_CIPHER_SUITE_CCMP; |
| case WPA_CIPHER_GCMP: |
| return WLAN_CIPHER_SUITE_GCMP; |
| case WPA_CIPHER_TKIP: |
| return WLAN_CIPHER_SUITE_TKIP; |
| case WPA_CIPHER_WEP104: |
| return WLAN_CIPHER_SUITE_WEP104; |
| case WPA_CIPHER_WEP40: |
| return WLAN_CIPHER_SUITE_WEP40; |
| case WPA_CIPHER_GTK_NOT_USED: |
| return WLAN_CIPHER_SUITE_NO_GROUP_ADDR; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int wpa_cipher_to_cipher_suites(unsigned int ciphers, u32 suites[], |
| int max_suites) |
| { |
| int num_suites = 0; |
| |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_CCMP_256) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_CCMP_256; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_GCMP_256) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_GCMP_256; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_CCMP) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_CCMP; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_GCMP) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_GCMP; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_TKIP) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_TKIP; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_WEP104) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_WEP104; |
| if (num_suites < max_suites && ciphers & WPA_CIPHER_WEP40) |
| suites[num_suites++] = WLAN_CIPHER_SUITE_WEP40; |
| |
| return num_suites; |
| } |
| |
| |
| static int issue_key_mgmt_set_key(struct wpa_driver_nl80211_data *drv, |
| const u8 *key, size_t key_len) |
| { |
| struct nl_msg *msg; |
| int ret; |
| |
| if (!(drv->capa.flags & WPA_DRIVER_FLAGS_KEY_MGMT_OFFLOAD)) |
| return 0; |
| |
| if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR)) || |
| nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA) || |
| nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, |
| QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY) || |
| nla_put(msg, NL80211_ATTR_VENDOR_DATA, key_len, key)) { |
| nl80211_nlmsg_clear(msg); |
| nlmsg_free(msg); |
| return -1; |
| } |
| ret = send_and_recv_msgs(drv, msg, NULL, (void *) -1); |
| if (ret) { |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Key management set key failed: ret=%d (%s)", |
| ret, strerror(-ret)); |
| } |
| |
| return ret; |
| } |
| |
| |
| static int wpa_driver_nl80211_set_key(const char *ifname, struct i802_bss *bss, |
| enum wpa_alg alg, const u8 *addr, |
| int key_idx, int set_tx, |
| const u8 *seq, size_t seq_len, |
| const u8 *key, size_t key_len) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| int ifindex; |
| struct nl_msg *msg; |
| int ret; |
| int tdls = 0; |
| |
| /* Ignore for P2P Device */ |
| if (drv->nlmode == NL80211_IFTYPE_P2P_DEVICE) |
| return 0; |
| |
| ifindex = if_nametoindex(ifname); |
| wpa_printf(MSG_DEBUG, "%s: ifindex=%d (%s) alg=%d addr=%p key_idx=%d " |
| "set_tx=%d seq_len=%lu key_len=%lu", |
| __func__, ifindex, ifname, alg, addr, key_idx, set_tx, |
| (unsigned long) seq_len, (unsigned long) key_len); |
| #ifdef CONFIG_TDLS |
| if (key_idx == -1) { |
| key_idx = 0; |
| tdls = 1; |
| } |
| #endif /* CONFIG_TDLS */ |
| |
| if (alg == WPA_ALG_PMK && |
| (drv->capa.flags & WPA_DRIVER_FLAGS_KEY_MGMT_OFFLOAD)) { |
| wpa_printf(MSG_DEBUG, "%s: calling issue_key_mgmt_set_key", |
| __func__); |
| ret = issue_key_mgmt_set_key(drv, key, key_len); |
| return ret; |
| } |
| |
| if (alg == WPA_ALG_NONE) { |
| msg = nl80211_ifindex_msg(drv, ifindex, 0, NL80211_CMD_DEL_KEY); |
| if (!msg) |
| return -ENOBUFS; |
| } else { |
| msg = nl80211_ifindex_msg(drv, ifindex, 0, NL80211_CMD_NEW_KEY); |
| if (!msg || |
| nla_put(msg, NL80211_ATTR_KEY_DATA, key_len, key) || |
| nla_put_u32(msg, NL80211_ATTR_KEY_CIPHER, |
| wpa_alg_to_cipher_suite(alg, key_len))) |
| goto fail; |
| wpa_hexdump_key(MSG_DEBUG, "nl80211: KEY_DATA", key, key_len); |
| } |
| |
| if (seq && seq_len) { |
| if (nla_put(msg, NL80211_ATTR_KEY_SEQ, seq_len, seq)) |
| goto fail; |
| wpa_hexdump(MSG_DEBUG, "nl80211: KEY_SEQ", seq, seq_len); |
| } |
| |
| if (addr && !is_broadcast_ether_addr(addr)) { |
| wpa_printf(MSG_DEBUG, " addr=" MACSTR, MAC2STR(addr)); |
| if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr)) |
| goto fail; |
| |
| if (alg != WPA_ALG_WEP && key_idx && !set_tx) { |
| wpa_printf(MSG_DEBUG, " RSN IBSS RX GTK"); |
| if (nla_put_u32(msg, NL80211_ATTR_KEY_TYPE, |
| NL80211_KEYTYPE_GROUP)) |
| goto fail; |
| } |
| } else if (addr && is_broadcast_ether_addr(addr)) { |
| struct nlattr *types; |
| |
| wpa_printf(MSG_DEBUG, " broadcast key"); |
| |
| types = nla_nest_start(msg, NL80211_ATTR_KEY_DEFAULT_TYPES); |
| if (!types || |
| nla_put_flag(msg, NL80211_KEY_DEFAULT_TYPE_MULTICAST)) |
| goto fail; |
| nla_nest_end(msg, types); |
| } |
| if (nla_put_u8(msg, NL80211_ATTR_KEY_IDX, key_idx)) |
| goto fail; |
| |
| ret = send_and_recv_msgs(drv, msg, NULL, key ? (void *) -1 : NULL); |
| if ((ret == -ENOENT || ret == -ENOLINK) && alg == WPA_ALG_NONE) |
| ret = 0; |
| if (ret) |
| wpa_printf(MSG_DEBUG, "nl80211: set_key failed; err=%d %s)", |
| ret, strerror(-ret)); |
| |
| /* |
| * If we failed or don't need to set the default TX key (below), |
| * we're done here. |
| */ |
| if (ret || !set_tx || alg == WPA_ALG_NONE || tdls) |
| return ret; |
| if (is_ap_interface(drv->nlmode) && addr && |
| !is_broadcast_ether_addr(addr)) |
| return ret; |
| |
| msg = nl80211_ifindex_msg(drv, ifindex, 0, NL80211_CMD_SET_KEY); |
| if (!msg || |
| nla_put_u8(msg, NL80211_ATTR_KEY_IDX, key_idx) || |
| nla_put_flag(msg, (alg == WPA_ALG_IGTK || |
| alg == WPA_ALG_BIP_GMAC_128 || |
| alg == WPA_ALG_BIP_GMAC_256 || |
| alg == WPA_ALG_BIP_CMAC_256) ? |
| NL80211_ATTR_KEY_DEFAULT_MGMT : |
| NL80211_ATTR_KEY_DEFAULT)) |
| goto fail; |
| if (addr && is_broadcast_ether_addr(addr)) { |
| struct nlattr *types; |
| |
| types = nla_nest_start(msg, NL80211_ATTR_KEY_DEFAULT_TYPES); |
| if (!types || |
| nla_put_flag(msg, NL80211_KEY_DEFAULT_TYPE_MULTICAST)) |
| goto fail; |
| nla_nest_end(msg, types); |
| } else if (addr) { |
| struct nlattr *types; |
| |
| types = nla_nest_start(msg, NL80211_ATTR_KEY_DEFAULT_TYPES); |
| if (!types || |
| nla_put_flag(msg, NL80211_KEY_DEFAULT_TYPE_UNICAST)) |
| goto fail; |
| nla_nest_end(msg, types); |
| } |
| |
| ret = send_and_recv_msgs(drv, msg, NULL, NULL); |
| if (ret == -ENOENT) |
| ret = 0; |
| if (ret) |
| wpa_printf(MSG_DEBUG, "nl80211: set_key default failed; " |
| "err=%d %s)", ret, strerror(-ret)); |
| return ret; |
| |
| fail: |
| nl80211_nlmsg_clear(msg); |
| nlmsg_free(msg); |
| return -ENOBUFS; |
| } |
| |
| |
| static int nl_add_key(struct nl_msg *msg, enum wpa_alg alg, |
| int key_idx, int defkey, |
| const u8 *seq, size_t seq_len, |
| const u8 *key, size_t key_len) |
| { |
| struct nlattr *key_attr = nla_nest_start(msg, NL80211_ATTR_KEY); |
| if (!key_attr) |
| return -1; |
| |
| if (defkey && alg == WPA_ALG_IGTK) { |
| if (nla_put_flag(msg, NL80211_KEY_DEFAULT_MGMT)) |
| return -1; |
| } else if (defkey) { |
| if (nla_put_flag(msg, NL80211_KEY_DEFAULT)) |
| return -1; |
| } |
| |
| if (nla_put_u8(msg, NL80211_KEY_IDX, key_idx) || |
| nla_put_u32(msg, NL80211_KEY_CIPHER, |
| wpa_alg_to_cipher_suite(alg, key_len)) || |
| (seq && seq_len && |
| nla_put(msg, NL80211_KEY_SEQ, seq_len, seq)) || |
| nla_put(msg, NL80211_KEY_DATA, key_len, key)) |
| return -1; |
| |
| nla_nest_end(msg, key_attr); |
| |
| return 0; |
| } |
| |
| |
| static int nl80211_set_conn_keys(struct wpa_driver_associate_params *params, |
| struct nl_msg *msg) |
| { |
| int i, privacy = 0; |
| struct nlattr *nl_keys, *nl_key; |
| |
| for (i = 0; i < 4; i++) { |
| if (!params->wep_key[i]) |
| continue; |
| privacy = 1; |
| break; |
| } |
| if (params->wps == WPS_MODE_PRIVACY) |
| privacy = 1; |
| if (params->pairwise_suite && |
| params->pairwise_suite != WPA_CIPHER_NONE) |
| privacy = 1; |
| |
| if (!privacy) |
| return 0; |
| |
| if (nla_put_flag(msg, NL80211_ATTR_PRIVACY)) |
| return -ENOBUFS; |
| |
| nl_keys = nla_nest_start(msg, NL80211_ATTR_KEYS); |
| if (!nl_keys) |
| return -ENOBUFS; |
| |
| for (i = 0; i < 4; i++) { |
| if (!params->wep_key[i]) |
| continue; |
| |
| nl_key = nla_nest_start(msg, i); |
| if (!nl_key || |
| nla_put(msg, NL80211_KEY_DATA, params->wep_key_len[i], |
| params->wep_key[i]) || |
| nla_put_u32(msg, NL80211_KEY_CIPHER, |
| params->wep_key_len[i] == 5 ? |
| WLAN_CIPHER_SUITE_WEP40 : |
| WLAN_CIPHER_SUITE_WEP104) || |
| nla_put_u8(msg, NL80211_KEY_IDX, i) || |
| (i == params->wep_tx_keyidx && |
| nla_put_flag(msg, NL80211_KEY_DEFAULT))) |
| return -ENOBUFS; |
| |
| nla_nest_end(msg, nl_key); |
| } |
| nla_nest_end(msg, nl_keys); |
| |
| return 0; |
| } |
| |
| |
| int wpa_driver_nl80211_mlme(struct wpa_driver_nl80211_data *drv, |
| const u8 *addr, int cmd, u16 reason_code, |
| int local_state_change) |
| { |
| int ret; |
| struct nl_msg *msg; |
| |
| if (!(msg = nl80211_drv_msg(drv, 0, cmd)) || |
| nla_put_u16(msg, NL80211_ATTR_REASON_CODE, reason_code) || |
| (addr && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr)) || |
| (local_state_change && |
| nla_put_flag(msg, NL80211_ATTR_LOCAL_STATE_CHANGE))) { |
| nlmsg_free(msg); |
| return -1; |
| } |
| |
| ret = send_and_recv_msgs(drv, msg, NULL, NULL); |
| if (ret) { |
| wpa_dbg(drv->ctx, MSG_DEBUG, |
| "nl80211: MLME command failed: reason=%u ret=%d (%s)", |
| reason_code, ret, strerror(-ret)); |
| } |
| return ret; |
| } |
| |
| |
| static int wpa_driver_nl80211_disconnect(struct wpa_driver_nl80211_data *drv, |
| int reason_code) |
| { |
| int ret; |
| |
| wpa_printf(MSG_DEBUG, "%s(reason_code=%s)", __func__, |
| reason2str(reason_code)); |
| nl80211_mark_disconnected(drv); |
| /* Disconnect command doesn't need BSSID - it uses cached value */ |
| ret = wpa_driver_nl80211_mlme(drv, NULL, NL80211_CMD_DISCONNECT, |
| reason_code, 0); |
| /* |
| * For locally generated disconnect, supplicant already generates a |
| * DEAUTH event, so ignore the event from NL80211. |
| */ |
| drv->ignore_next_local_disconnect = ret == 0; |
| |
| return ret; |
| } |
| |
| |
| static int wpa_driver_nl80211_deauthenticate(struct i802_bss *bss, |
| const u8 *addr, int reason_code) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| int ret; |
| |
| if (drv->nlmode == NL80211_IFTYPE_ADHOC) { |
| nl80211_mark_disconnected(drv); |
| return nl80211_leave_ibss(drv, 1); |
| } |
| if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) |
| return wpa_driver_nl80211_disconnect(drv, reason_code); |
| wpa_printf(MSG_DEBUG, "%s(addr=" MACSTR " reason_code=%s)", |
| __func__, MAC2STR(addr), reason2str(reason_code)); |
| nl80211_mark_disconnected(drv); |
| ret = wpa_driver_nl80211_mlme(drv, addr, NL80211_CMD_DEAUTHENTICATE, |
| reason_code, 0); |
| /* |
| * For locally generated deauthenticate, supplicant already generates a |
| * DEAUTH event, so ignore the event from NL80211. |
| */ |
| drv->ignore_next_local_deauth = ret == 0; |
| return ret; |
| } |
| |
| |
| static void nl80211_copy_auth_params(struct wpa_driver_nl80211_data *drv, |
| struct wpa_driver_auth_params *params) |
| { |
| int i; |
| |
| drv->auth_freq = params->freq; |
| drv->auth_alg = params->auth_alg; |
| drv->auth_wep_tx_keyidx = params->wep_tx_keyidx; |
| drv->auth_local_state_change = params->local_state_change; |
| drv->auth_p2p = params->p2p; |
| |
| if (params->bssid) |
| os_memcpy(drv->auth_bssid_, params->bssid, ETH_ALEN); |
| else |
| os_memset(drv->auth_bssid_, 0, ETH_ALEN); |
| |
| if (params->ssid) { |
| os_memcpy(drv->auth_ssid, params->ssid, params->ssid_len); |
| drv->auth_ssid_len = params->ssid_len; |
| } else |
| drv->auth_ssid_len = 0; |
| |
| |
| os_free(drv->auth_ie); |
| drv->auth_ie = NULL; |
| drv->auth_ie_len = 0; |
| if (params->ie) { |
| drv->auth_ie = os_malloc(params->ie_len); |
| if (drv->auth_ie) { |
| os_memcpy(drv->auth_ie, params->ie, params->ie_len); |
| drv->auth_ie_len = params->ie_len; |
| } |
| } |
| |
| for (i = 0; i < 4; i++) { |
| if (params->wep_key[i] && params->wep_key_len[i] && |
| params->wep_key_len[i] <= 16) { |
| os_memcpy(drv->auth_wep_key[i], params->wep_key[i], |
| params->wep_key_len[i]); |
| drv->auth_wep_key_len[i] = params->wep_key_len[i]; |
| } else |
| drv->auth_wep_key_len[i] = 0; |
| } |
| } |
| |
| |
| static void nl80211_unmask_11b_rates(struct i802_bss *bss) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| |
| if (is_p2p_net_interface(drv->nlmode) || !drv->disabled_11b_rates) |
| return; |
| |
| /* |
| * Looks like we failed to unmask 11b rates previously. This could |
| * happen, e.g., if the interface was down at the point in time when a |
| * P2P group was terminated. |
| */ |
| wpa_printf(MSG_DEBUG, |
| "nl80211: Interface %s mode is for non-P2P, but 11b rates were disabled - re-enable them", |
| bss->ifname); |
| nl80211_disable_11b_rates(drv, drv->ifindex, 0); |
| } |
| |
| |
| static int wpa_driver_nl80211_authenticate( |
| struct i802_bss *bss, struct wpa_driver_auth_params *params) |
| { |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| int ret = -1, i; |
| struct nl_msg *msg; |
| enum nl80211_auth_type type; |
| enum nl80211_iftype nlmode; |
| int count = 0; |
| int is_retry; |
| |
| nl80211_unmask_11b_rates(bss); |
| |
| is_retry = drv->retry_auth; |
| drv->retry_auth = 0; |
| drv->ignore_deauth_event = 0; |
| |
| nl80211_mark_disconnected(drv); |
| os_memset(drv->auth_bssid, 0, ETH_ALEN); |
| if (params->bssid) |
| os_memcpy(drv->auth_attempt_bssid, params->bssid, ETH_ALEN); |
| else |
| os_memset(drv->auth_attempt_bssid, 0, ETH_ALEN); |
| /* FIX: IBSS mode */ |
| nlmode = params->p2p ? |
| NL80211_IFTYPE_P2P_CLIENT : NL80211_IFTYPE_STATION; |
| if (drv->nlmode != nlmode && |
| wpa_driver_nl80211_set_mode(bss, nlmode) < 0) |
| return -1; |
| |
| retry: |
| wpa_printf(MSG_DEBUG, "nl80211: Authenticate (ifindex=%d)", |
| drv->ifindex); |
| |
| msg = nl80211_drv_msg(drv, 0, NL80211_CMD_AUTHENTICATE); |
| if (!msg) |
| goto fail; |
| |
| for (i = 0; i < 4; i++) { |
| if (!params->wep_key[i]) |
| continue; |
| wpa_driver_nl80211_set_key(bss->ifname, bss, WPA_ALG_WEP, |
| NULL, i, |
| i == params->wep_tx_keyidx, NULL, 0, |
| params->wep_key[i], |
| params->wep_key_len[i]); |
| if (params->wep_tx_keyidx != i) |
| continue; |
| if (nl_add_key(msg, WPA_ALG_WEP, i, 1, NULL, 0, |
| params->wep_key[i], params->wep_key_len[i])) |
| goto fail; |
| } |
| |
| if (params->bssid) { |
| wpa_printf(MSG_DEBUG, " * bssid=" MACSTR, |
| MAC2STR(params->bssid)); |
| if (nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid)) |
| goto fail; |
| } |
| if (params->freq) { |
| wpa_printf(MSG_DEBUG, " * freq=%d", params->freq); |
| if (nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, params->freq)) |
| goto fail; |
| } |
| if (params->ssid) { |
| wpa_hexdump_ascii(MSG_DEBUG, " * SSID", |
| params->ssid, params->ssid_len); |
| if (nla_put(msg, NL80211_ATTR_SSID, params->ssid_len, |
| params->ssid)) |
| goto fail; |
| } |
| wpa_hexdump(MSG_DEBUG, " * IEs", params->ie, params->ie_len); |
| if (params->ie && |
| nla_put(msg, NL80211_ATTR_IE, params->ie_len, params->ie)) |
| goto fail; |
| if (params->sae_data) { |
| wpa_hexdump(MSG_DEBUG, " * SAE data", params->sae_data, |
| params->sae_data_len); |
| if (nla_put(msg, NL80211_ATTR_SAE_DATA, params->sae_data_len, |
| params->sae_data)) |
| goto fail; |
| } |
| if (params->auth_alg & WPA_AUTH_ALG_OPEN) |
| type = NL80211_AUTHTYPE_OPEN_SYSTEM; |
| else if (params->auth_alg & WPA_AUTH_ALG_SHARED) |
| type = NL80211_AUTHTYPE_SHARED_KEY; |
| else if (params->auth_alg & WPA_AUTH_ALG_LEAP) |
| type = NL80211_AUTHTYPE_NETWORK_EAP; |
| else if (params->auth_alg & WPA_AUTH_ALG_FT) |
| type = NL80211_AUTHTYPE_FT; |
| else if (params->auth_alg & WPA_AUTH_ALG_SAE) |
| type = NL80211_AUTHTYPE_SAE; |
| else |
| goto fail; |
| wpa_printf(MSG_DEBUG, " * Auth Type %d", type); |
| if (nla_put_u32(msg, NL80211_ATTR_AUTH_TYPE, type)) |
| goto fail; |
| if (params->local_state_change) { |
| wpa_printf(MSG_DEBUG, " * Local state change only"); |
| if (nla_put_flag(msg, NL80211_ATTR_LOCAL_STATE_CHANGE)) |
| goto fail; |
| } |
|