summaryrefslogtreecommitdiff
path: root/src/platform/wifi/wifi-utils-nl80211.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/wifi/wifi-utils-nl80211.c')
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c971
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;
+}
+