diff options
Diffstat (limited to 'src/platform/wifi/wifi-utils-nl80211.c')
-rw-r--r-- | src/platform/wifi/wifi-utils-nl80211.c | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c new file mode 100644 index 000000000..25ebd1b3c --- /dev/null +++ b/src/platform/wifi/wifi-utils-nl80211.c @@ -0,0 +1,971 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (C) 2005 - 2011 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + * Copyright (C) 2011 Intel Corporation. All rights reserved. + */ + +#include <config.h> +#include <errno.h> +#include <string.h> +#include <sys/ioctl.h> +#include <net/ethernet.h> +#include <unistd.h> +#include <math.h> + +#include <glib.h> + +#include <netlink/genl/genl.h> +#include <netlink/genl/family.h> +#include <netlink/genl/ctrl.h> + +#include <linux/nl80211.h> + +#include "wifi-utils-private.h" +#include "wifi-utils-nl80211.h" +#include "nm-platform.h" +#include "nm-logging.h" +#include "nm-utils.h" + +typedef struct { + WifiData parent; + struct nl_sock *nl_sock; + int id; + struct nl_cb *nl_cb; + guint32 *freqs; + int num_freqs; + int phy; +} WifiDataNl80211; + +static int +ack_handler (struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_STOP; +} + +static int +finish_handler (struct nl_msg *msg, void *arg) +{ + int *done = arg; + *done = 1; + return NL_SKIP; +} + +static int +error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *done = arg; + *done = err->error; + return NL_SKIP; +} + +static struct nl_msg * +_nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags) +{ + struct nl_msg *msg; + + msg = nlmsg_alloc (); + if (msg) { + genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0); + NLA_PUT_U32 (msg, NL80211_ATTR_IFINDEX, ifindex); + if (phy != -1) + NLA_PUT_U32 (msg, NL80211_ATTR_WIPHY, phy); + } + return msg; + + nla_put_failure: + nlmsg_free (msg); + return NULL; +} + +static struct nl_msg * +nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags) +{ + return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags); +} + +/* NOTE: this function consumes 'msg' */ +static int +_nl80211_send_and_recv (struct nl_sock *nl_sock, + struct nl_cb *nl_cb, + struct nl_msg *msg, + int (*valid_handler) (struct nl_msg *, void *), + void *valid_data) +{ + struct nl_cb *cb; + int err, done; + + g_return_val_if_fail (msg != NULL, -ENOMEM); + + cb = nl_cb_clone (nl_cb); + if (!cb) { + err = -ENOMEM; + goto out; + } + + err = nl_send_auto_complete (nl_sock, msg); + if (err < 0) + goto out; + + done = 0; + nl_cb_err (cb, NL_CB_CUSTOM, error_handler, &done); + nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &done); + nl_cb_set (cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &done); + if (valid_handler) + nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data); + + /* Loop until one of our NL callbacks says we're done; on success + * done will be 1, on error it will be < 0. + */ + while (!done) { + err = nl_recvmsgs (nl_sock, cb); + if (err && err != -NLE_AGAIN) { + /* Kernel scan list can change while we are dumping it, as new scan + * results from H/W can arrive. BSS info is assured to be consistent + * and we don't need consistent view of whole scan list. Hence do + * not warn on DUMP_INTR error for get scan command. + */ + if (err == -NLE_DUMP_INTR && + genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN) + break; + + nm_log_warn (LOGD_WIFI, "nl_recvmsgs() error: (%d) %s", + err, nl_geterror (err)); + break; + } + } + if (err == 0 && done < 0) + err = done; + + out: + nl_cb_put (cb); + nlmsg_free (msg); + return err; +} + +static int +nl80211_send_and_recv (WifiDataNl80211 *nl80211, + struct nl_msg *msg, + int (*valid_handler) (struct nl_msg *, void *), + void *valid_data) +{ + return _nl80211_send_and_recv (nl80211->nl_sock, nl80211->nl_cb, msg, + valid_handler, valid_data); +} + +static void +wifi_nl80211_deinit (WifiData *parent) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) parent; + + if (nl80211->nl_sock) + nl_socket_free (nl80211->nl_sock); + if (nl80211->nl_cb) + nl_cb_put (nl80211->nl_cb); + g_free (nl80211->freqs); +} + +struct nl80211_iface_info { + NM80211Mode mode; +}; + +static int +nl80211_iface_info_handler (struct nl_msg *msg, void *arg) +{ + struct nl80211_iface_info *info = arg; + struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg)); + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + + if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0), + genlmsg_attrlen (gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (!tb[NL80211_ATTR_IFTYPE]) + return NL_SKIP; + + switch (nla_get_u32 (tb[NL80211_ATTR_IFTYPE])) { + case NL80211_IFTYPE_ADHOC: + info->mode = NM_802_11_MODE_ADHOC; + break; + case NL80211_IFTYPE_AP: + info->mode = NM_802_11_MODE_AP; + break; + case NL80211_IFTYPE_STATION: + info->mode = NM_802_11_MODE_INFRA; + break; + } + + return NL_SKIP; +} + +static NM80211Mode +wifi_nl80211_get_mode (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl80211_iface_info iface_info = { + .mode = NM_802_11_MODE_UNKNOWN, + }; + struct nl_msg *msg; + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_INTERFACE, 0); + + if (nl80211_send_and_recv (nl80211, msg, nl80211_iface_info_handler, + &iface_info) < 0) + return NM_802_11_MODE_UNKNOWN; + + return iface_info.mode; +} + +static gboolean +wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl_msg *msg; + int err; + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_INTERFACE, 0); + + switch (mode) { + case NM_802_11_MODE_INFRA: + NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION); + break; + case NM_802_11_MODE_ADHOC: + NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_ADHOC); + break; + case NM_802_11_MODE_AP: + NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP); + break; + default: + g_assert_not_reached (); + } + + err = nl80211_send_and_recv (nl80211, msg, NULL, NULL); + return err ? FALSE : TRUE; + + nla_put_failure: + nlmsg_free (msg); + return FALSE; +} + +/* @divisor: pass what value @xbm should be divided by to get dBm */ +static guint32 +nl80211_xbm_to_percent (gint32 xbm, guint32 divisor) +{ +#define NOISE_FLOOR_DBM -90 +#define SIGNAL_MAX_DBM -20 + + xbm /= divisor; + xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM); + + return 100 - 70 * (((float) SIGNAL_MAX_DBM - (float) xbm) / + ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM)); +} + +struct nl80211_bss_info { + guint32 freq; + guint8 bssid[ETH_ALEN]; + guint8 ssid[32]; + guint32 ssid_len; + guint32 beacon_signal; + gboolean valid; +}; + +#define WLAN_EID_SSID 0 + +static void +find_ssid (guint8 *ies, guint32 ies_len, + guint8 **ssid, guint32 *ssid_len) +{ + *ssid = NULL; + *ssid_len = 0; + + while (ies_len > 2 && ies[0] != WLAN_EID_SSID) { + ies_len -= ies[1] + 2; + ies += ies[1] + 2; + } + if (ies_len < 2) + return; + if (ies_len < 2 + ies[1]) + return; + + *ssid_len = ies[1]; + *ssid = ies + 2; +} + +static int +nl80211_bss_dump_handler (struct nl_msg *msg, void *arg) +{ + struct nl80211_bss_info *info = arg; + struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg)); + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct nlattr *bss[NL80211_BSS_MAX + 1]; + static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { + [NL80211_BSS_TSF] = { .type = NLA_U64 }, + [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, + [NL80211_BSS_BSSID] = { }, + [NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 }, + [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, + [NL80211_BSS_INFORMATION_ELEMENTS] = { }, + [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, + [NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 }, + [NL80211_BSS_STATUS] = { .type = NLA_U32 }, + }; + guint32 status; + + if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0), + genlmsg_attrlen (gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_BSS] == NULL) + return NL_SKIP; + + if (nla_parse_nested (bss, NL80211_BSS_MAX, + tb[NL80211_ATTR_BSS], + bss_policy)) + return NL_SKIP; + + if (bss[NL80211_BSS_STATUS] == NULL) + return NL_SKIP; + + status = nla_get_u32 (bss[NL80211_BSS_STATUS]); + + if (status != NL80211_BSS_STATUS_ASSOCIATED && + status != NL80211_BSS_STATUS_IBSS_JOINED) + return NL_SKIP; + + if (bss[NL80211_BSS_BSSID] == NULL) + return NL_SKIP; + memcpy(info->bssid, nla_data (bss[NL80211_BSS_BSSID]), ETH_ALEN); + + if (bss[NL80211_BSS_FREQUENCY]) + info->freq = nla_get_u32 (bss[NL80211_BSS_FREQUENCY]); + + if (bss[NL80211_BSS_SIGNAL_UNSPEC]) + info->beacon_signal = + nla_get_u8 (bss[NL80211_BSS_SIGNAL_UNSPEC]); + + if (bss[NL80211_BSS_SIGNAL_MBM]) + info->beacon_signal = + nl80211_xbm_to_percent (nla_get_u32 (bss[NL80211_BSS_SIGNAL_MBM]), 100); + + if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) { + guint8 *ssid; + guint32 ssid_len; + + find_ssid(nla_data (bss[NL80211_BSS_INFORMATION_ELEMENTS]), + nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]), + &ssid, &ssid_len); + if (ssid && ssid_len && ssid_len <= sizeof (info->ssid)) { + memcpy (info->ssid, ssid, ssid_len); + info->ssid_len = ssid_len; + } + } + + info->valid = TRUE; + + return NL_SKIP; +} + +static void +nl80211_get_bss_info (WifiDataNl80211 *nl80211, + struct nl80211_bss_info *bss_info) +{ + struct nl_msg *msg; + + memset(bss_info, 0, sizeof (*bss_info)); + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_SCAN, NLM_F_DUMP); + + nl80211_send_and_recv (nl80211, msg, nl80211_bss_dump_handler, bss_info); +} + +static guint32 +wifi_nl80211_get_freq (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl80211_bss_info bss_info; + + nl80211_get_bss_info (nl80211, &bss_info); + + return bss_info.freq; +} + +static guint32 +wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + int i; + + for (i = 0; i < nl80211->num_freqs; i++) { + while (*freqs) { + if (nl80211->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + return 0; +} + +static GByteArray * +wifi_nl80211_get_ssid (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + GByteArray *array = NULL; + struct nl80211_bss_info bss_info; + + nl80211_get_bss_info (nl80211, &bss_info); + + if (bss_info.valid) { + array = g_byte_array_sized_new (bss_info.ssid_len); + g_byte_array_append (array, (const guint8 *) bss_info.ssid, + bss_info.ssid_len); + } + + return array; +} + +static gboolean +wifi_nl80211_get_bssid (WifiData *data, struct ether_addr *out_bssid) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl80211_bss_info bss_info; + + nl80211_get_bss_info (nl80211, &bss_info); + + if (bss_info.valid) + memcpy(out_bssid, bss_info.bssid, ETH_ALEN); + + return bss_info.valid; +} + +struct nl80211_station_info { + guint32 txrate; + gboolean txrate_valid; + guint8 signal; + gboolean signal_valid; +}; + +static int +nl80211_station_handler (struct nl_msg *msg, void *arg) +{ + struct nl80211_station_info *info = arg; + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg)); + struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; + struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1]; + static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = { + [NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 }, + [NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 }, + [NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 }, + [NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 }, + [NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 }, + [NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 }, + [NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED }, + [NL80211_STA_INFO_LLID] = { .type = NLA_U16 }, + [NL80211_STA_INFO_PLID] = { .type = NLA_U16 }, + [NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 }, + }; + + 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 }, + }; + + if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0), + genlmsg_attrlen (gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_STA_INFO] == NULL) + return NL_SKIP; + + if (nla_parse_nested (sinfo, NL80211_STA_INFO_MAX, + tb[NL80211_ATTR_STA_INFO], + stats_policy)) + return NL_SKIP; + + if (sinfo[NL80211_STA_INFO_TX_BITRATE] == NULL) + return NL_SKIP; + + if (nla_parse_nested (rinfo, NL80211_RATE_INFO_MAX, + sinfo[NL80211_STA_INFO_TX_BITRATE], + rate_policy)) + return NL_SKIP; + + if (rinfo[NL80211_RATE_INFO_BITRATE] == NULL) + return NL_SKIP; + + /* convert from nl80211's units of 100kbps to NM's kbps */ + info->txrate = nla_get_u16 (rinfo[NL80211_RATE_INFO_BITRATE]) * 100; + info->txrate_valid = TRUE; + + if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) { + info->signal = nl80211_xbm_to_percent ((gint8) nla_get_u8 (sinfo[NL80211_STA_INFO_SIGNAL]), 1); + info->signal_valid = TRUE; + } + + return NL_SKIP; +} + +static void +nl80211_get_ap_info (WifiDataNl80211 *nl80211, + struct nl80211_station_info *sta_info) +{ + struct nl_msg *msg; + struct nl80211_bss_info bss_info; + + memset(sta_info, 0, sizeof (*sta_info)); + + nl80211_get_bss_info (nl80211, &bss_info); + if (!bss_info.valid) + return; + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_STATION, 0); + if (msg) { + NLA_PUT (msg, NL80211_ATTR_MAC, ETH_ALEN, bss_info.bssid); + + nl80211_send_and_recv (nl80211, msg, nl80211_station_handler, sta_info); + if (!sta_info->signal_valid) { + /* Fall back to bss_info signal quality (both are in percent) */ + sta_info->signal = bss_info.beacon_signal; + } + } + + return; + + nla_put_failure: + nlmsg_free (msg); + return; +} + +static guint32 +wifi_nl80211_get_rate (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl80211_station_info sta_info; + + nl80211_get_ap_info (nl80211, &sta_info); + + return sta_info.txrate; +} + +static int +wifi_nl80211_get_qual (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl80211_station_info sta_info; + + nl80211_get_ap_info (nl80211, &sta_info); + return sta_info.signal; +} + +#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS +static gboolean +wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl_msg *msg; + int err; + + msg = nl80211_alloc_msg (nl80211, + running ? NL80211_CMD_CRIT_PROTOCOL_START : + NL80211_CMD_CRIT_PROTOCOL_STOP, + 0); + /* Despite the DHCP name, we're using this for any type of IP addressing, + * DHCPv4, DHCPv6, and IPv6 SLAAC. + */ + NLA_PUT_U16 (msg, NL80211_ATTR_CRIT_PROT_ID, NL80211_CRIT_PROTO_DHCP); + if (running) { + /* Give DHCP 5 seconds to complete */ + NLA_PUT_U16 (msg, NL80211_ATTR_MAX_CRIT_PROT_DURATION, 5000); + } + + err = nl80211_send_and_recv (nl80211, msg, NULL, NULL); + return err ? FALSE : TRUE; + +nla_put_failure: + nlmsg_free (msg); + return FALSE; +} +#endif + +struct nl80211_wowlan_info { + gboolean enabled; +}; + +static int +nl80211_wowlan_handler (struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg)); + struct nl80211_wowlan_info *info = arg; + + info->enabled = FALSE; + + if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen (gnlh, 0), NULL) < 0) + return NL_SKIP; + + if (tb[NL80211_ATTR_WOWLAN_TRIGGERS]) + info->enabled = TRUE; + + return NL_SKIP; +} + +static gboolean +wifi_nl80211_get_wowlan (WifiData *data) +{ + WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data; + struct nl_msg *msg; + struct nl80211_wowlan_info info; + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0); + nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info); + + return info.enabled; +} + +struct nl80211_device_info { + int phy; + guint32 *freqs; + int num_freqs; + guint32 caps; + gboolean can_scan; + gboolean can_scan_ssid; + gboolean supported; + gboolean success; + gboolean can_wowlan; +}; + +#define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00 +#define WLAN_CIPHER_SUITE_WEP40 0x000FAC01 +#define WLAN_CIPHER_SUITE_TKIP 0x000FAC02 +#define WLAN_CIPHER_SUITE_CCMP 0x000FAC04 +#define WLAN_CIPHER_SUITE_WEP104 0x000FAC05 +#define WLAN_CIPHER_SUITE_AES_CMAC 0x000FAC06 +#define WLAN_CIPHER_SUITE_GCMP 0x000FAC08 +#define WLAN_CIPHER_SUITE_SMS4 0x00147201 + +static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg)); + struct nl80211_device_info *info = arg; + struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; + struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; + struct nlattr *nl_band; + struct nlattr *nl_freq; + int rem_freq; + int rem_band; + int freq_idx; + static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = { + [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 }, + [NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 }, + }; + + if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen (gnlh, 0), NULL) < 0) + return NL_SKIP; + + if ( tb[NL80211_ATTR_WIPHY] == NULL + || tb[NL80211_ATTR_WIPHY_BANDS] == NULL) + return NL_SKIP; + + info->phy = nla_get_u32 (tb[NL80211_ATTR_WIPHY]); + + if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) { + info->can_scan_ssid = + nla_get_u8 (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0; + } else { + /* old kernel that only had mac80211, so assume it can */ + info->can_scan_ssid = TRUE; + } + + if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) { + struct nlattr *nl_cmd; + int i; + + nla_for_each_nested (nl_cmd, tb[NL80211_ATTR_SUPPORTED_COMMANDS], i) { + switch (nla_get_u32 (nl_cmd)) { + case NL80211_CMD_TRIGGER_SCAN: + info->can_scan = TRUE; + break; + case NL80211_CMD_CONNECT: + case NL80211_CMD_AUTHENTICATE: + /* Only devices that support CONNECT or AUTH actually support + * 802.11, unlike say ipw2x00 (up to at least kernel 3.4) which + * has minimal info support, but no actual command support. + * This check mirrors what wpa_supplicant does to determine + * whether or not to use the nl80211 driver. + */ + info->supported = TRUE; + break; + default: + break; + } + } + } + + info->num_freqs = 0; + + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band, + NULL) < 0) + return NL_SKIP; + + nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], + rem_freq) { + nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX, + nl_freq, freq_policy); + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->num_freqs++; + } + } + + info->freqs = g_malloc0 (sizeof (guint32) * info->num_freqs); + + freq_idx = 0; + nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band, + NULL) < 0) + return NL_SKIP; + + nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], + rem_freq) { + nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX, + nl_freq, freq_policy); + + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + + info->freqs[freq_idx] = + nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + freq_idx++; + } + } + + if (tb[NL80211_ATTR_CIPHER_SUITES]) { + int num; + int i; + __u32 *ciphers = nla_data (tb[NL80211_ATTR_CIPHER_SUITES]); + + num = nla_len (tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof (__u32); + for (i = 0; i < num; i++) { + switch (ciphers[i]) { + case WLAN_CIPHER_SUITE_WEP40: + info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40; + break; + case WLAN_CIPHER_SUITE_WEP104: + info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + break; + case WLAN_CIPHER_SUITE_TKIP: + info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | + NM_WIFI_DEVICE_CAP_WPA); + break; + case WLAN_CIPHER_SUITE_CCMP: + info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_CCMP | + NM_WIFI_DEVICE_CAP_RSN); + break; + case WLAN_CIPHER_SUITE_AES_CMAC: + case WLAN_CIPHER_SUITE_GCMP: + case WLAN_CIPHER_SUITE_SMS4: + break; + default: + nm_log_dbg (LOGD_HW | LOGD_WIFI, "Don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.", ciphers[i]); + break; + } + } + } + + if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) { + struct nlattr *nl_mode; + int i; + + nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) { + if (nla_type (nl_mode) == NL80211_IFTYPE_AP) + info->caps |= NM_WIFI_DEVICE_CAP_AP; + else if (nla_type (nl_mode) == NL80211_IFTYPE_ADHOC) + info->caps |= NM_WIFI_DEVICE_CAP_ADHOC; + } + } + + if (tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]) + info->can_wowlan = TRUE; + + info->success = TRUE; + + return NL_SKIP; +} + +WifiData * +wifi_nl80211_init (const char *iface, int ifindex) +{ + WifiDataNl80211 *nl80211; + struct nl_msg *msg; + struct nl80211_device_info device_info = {}; + + nl80211 = wifi_data_new (iface, ifindex, sizeof (*nl80211)); + nl80211->parent.get_mode = wifi_nl80211_get_mode; + nl80211->parent.set_mode = wifi_nl80211_set_mode; + nl80211->parent.get_freq = wifi_nl80211_get_freq; + nl80211->parent.find_freq = wifi_nl80211_find_freq; + nl80211->parent.get_ssid = wifi_nl80211_get_ssid; + nl80211->parent.get_bssid = wifi_nl80211_get_bssid; + nl80211->parent.get_rate = wifi_nl80211_get_rate; + nl80211->parent.get_qual = wifi_nl80211_get_qual; +#if HAVE_NL80211_CRITICAL_PROTOCOL_CMDS + nl80211->parent.indicate_addressing_running = wifi_nl80211_indicate_addressing_running; +#endif + nl80211->parent.deinit = wifi_nl80211_deinit; + + nl80211->nl_sock = nl_socket_alloc (); + if (nl80211->nl_sock == NULL) + goto error; + + if (genl_connect (nl80211->nl_sock)) + goto error; + + nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211"); + if (nl80211->id < 0) + goto error; + + nl80211->nl_cb = nl_cb_alloc (NL_CB_DEFAULT); + if (nl80211->nl_cb == NULL) + goto error; + + nl80211->phy = -1; + + msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WIPHY, 0); + + if (nl80211_send_and_recv (nl80211, msg, nl80211_wiphy_info_handler, + &device_info) < 0) { + nm_log_dbg (LOGD_HW | LOGD_WIFI, + "(%s): NL80211_CMD_GET_WIPHY request failed", + nl80211->parent.iface); + goto error; + } + + if (!device_info.success) { + nm_log_dbg (LOGD_HW | LOGD_WIFI, + "(%s): NL80211_CMD_GET_WIPHY request indicated failure", + nl80211->parent.iface); + goto error; + } + + if (!device_info.supported) { + nm_log_dbg (LOGD_HW | LOGD_WIFI, + "(%s): driver does not fully support nl80211, falling back to WEXT", + nl80211->parent.iface); + goto error; + } + + if (!device_info.can_scan_ssid) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): driver does not support SSID scans", + nl80211->parent.iface); + goto error; + } + + if (device_info.num_freqs == 0 || device_info.freqs == NULL) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): driver reports no supported frequencies", + nl80211->parent.iface); + goto error; + } + + if (device_info.caps == 0) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): driver doesn't report support of any encryption", + nl80211->parent.iface); + goto error; + } + + nl80211->phy = device_info.phy; + nl80211->freqs = device_info.freqs; + nl80211->num_freqs = device_info.num_freqs; + nl80211->parent.caps = device_info.caps; + + if (device_info.can_wowlan) + nl80211->parent.get_wowlan = wifi_nl80211_get_wowlan; + + nm_log_info (LOGD_HW | LOGD_WIFI, + "(%s): using nl80211 for WiFi device control", + nl80211->parent.iface); + + return (WifiData *) nl80211; + +error: + wifi_utils_deinit ((WifiData *) nl80211); + return NULL; +} + +gboolean +wifi_nl80211_is_wifi (const char *iface) +{ + struct nl_sock *nl_sock; + struct nl_cb *nl_cb = NULL; + struct nl_msg *msg = NULL; + int id, ifindex; + struct nl80211_iface_info iface_info = { + .mode = NM_802_11_MODE_UNKNOWN, + }; + gboolean is_wifi = FALSE; + + nl_sock = nl_socket_alloc (); + if (nl_sock == NULL) + return FALSE; + + if (genl_connect (nl_sock)) + goto error; + + ifindex = nm_platform_link_get_ifindex (iface); + if (ifindex < 0) + goto error; + + id = genl_ctrl_resolve (nl_sock, "nl80211"); + if (id < 0) + goto error; + + nl_cb = nl_cb_alloc (NL_CB_DEFAULT); + if (nl_cb) { + msg = _nl80211_alloc_msg (id, ifindex, -1, NL80211_CMD_GET_INTERFACE, 0); + if (_nl80211_send_and_recv (nl_sock, + nl_cb, + msg, + nl80211_iface_info_handler, + &iface_info) >= 0) + is_wifi = (iface_info.mode != NM_802_11_MODE_UNKNOWN); + } + + error: + if (nl_cb) + nl_cb_put (nl_cb); + nl_socket_free (nl_sock); + return is_wifi; +} + |