devices: add device id for Realtek RTL8188CU and RTL8188FTV
[project/iwinfo.git] / iwinfo_nl80211.c
index be58c56118c66bbfac82949711fdff79305e6cd3..220024955acce647410a8992f7fa19ae183c6b86 100644 (file)
  * Parts of this code are derived from the Linux iw utility.
  */
 
+#include <sys/stat.h>
 #include <limits.h>
 #include <glob.h>
 #include <fnmatch.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
 #include "iwinfo_nl80211.h"
 
 #define min(x, y) ((x) < (y)) ? (x) : (y)
 
+#define BIT(x) (1ULL<<(x))
+
 static struct nl80211_state *nls = NULL;
 
 static void nl80211_close(void)
@@ -147,6 +153,23 @@ static int nl80211_readstr(const char *path, char *buffer, int length)
        return rv;
 }
 
+static int nl80211_get_band(int nla_type)
+{
+       switch (nla_type)
+       {
+       case NL80211_BAND_2GHZ:
+               return IWINFO_BAND_24;
+       case NL80211_BAND_5GHZ:
+               return IWINFO_BAND_5;
+       case NL80211_BAND_6GHZ:
+               return IWINFO_BAND_6;
+       case NL80211_BAND_60GHZ:
+               return IWINFO_BAND_60;
+       }
+
+       return 0;
+}
+
 
 static int nl80211_msg_error(struct sockaddr_nl *nla,
        struct nlmsgerr *err, void *arg)
@@ -214,10 +237,6 @@ static struct nl80211_msg_conveyor * nl80211_new(struct genl_family *family,
        return &cv;
 
 err:
-nla_put_failure:
-       if (cb)
-               nl_cb_put(cb);
-
        if (req)
                nlmsg_free(req);
 
@@ -232,41 +251,123 @@ static struct nl80211_msg_conveyor * nl80211_ctl(int cmd, int flags)
        return nl80211_new(nls->nlctrl, cmd, flags);
 }
 
-static int nl80211_phy_idx_from_uci_path(struct uci_section *s)
+static const char *nl80211_phy_path_str(const char *phyname)
 {
-       const char *opt;
-       char buf[128];
+       static char path[PATH_MAX];
+       const char *prefix = "/sys/devices/";
+       int prefix_len = strlen(prefix);
+       int buf_len, offset;
+       struct dirent *e;
+       char buf[512], *link;
+       int phy_idx;
+       int seq = 0;
+       DIR *d;
+
+       snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", phyname);
+       phy_idx = nl80211_readint(buf);
+       if (phy_idx < 0)
+               return NULL;
+
+       buf_len = snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/device", phyname);
+       link = realpath(buf, path);
+       if (!link)
+               return NULL;
+
+       if (strncmp(link, prefix, prefix_len) != 0)
+               return NULL;
+
+       link += prefix_len;
+
+       prefix = "platform/";
+       prefix_len = strlen(prefix);
+       if (!strncmp(link, prefix, prefix_len) && strstr(link, "/pci"))
+               link += prefix_len;
+
+       snprintf(buf + buf_len, sizeof(buf) - buf_len, "/ieee80211");
+       d = opendir(buf);
+       if (!d)
+               return link;
+
+       while ((e = readdir(d)) != NULL) {
+               int cur_idx;
+
+               snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", e->d_name);
+               cur_idx = nl80211_readint(buf);
+               if (cur_idx < 0)
+                       continue;
+
+               if (cur_idx >= phy_idx)
+                       continue;
+
+               seq++;
+       }
+
+       closedir(d);
+
+       if (!seq)
+               return link;
+
+       offset = link - path + strlen(link);
+       snprintf(path + offset, sizeof(path) - offset, "+%d", seq);
+
+       return link;
+}
+
+static int nl80211_phy_idx_from_path(const char *path)
+{
+       char buf[512];
+       struct dirent *e;
+       const char *cur_path;
+       int cur_path_len;
+       int path_len;
        int idx = -1;
-       glob_t gl;
+       DIR *d;
 
-       opt = uci_lookup_option_string(uci_ctx, s, "path");
-       if (!opt)
+       if (!path)
                return -1;
 
-       snprintf(buf, sizeof(buf), "/sys/devices/%s/ieee80211/*/index", opt);  /**/
-       if (glob(buf, 0, NULL, &gl))
+       path_len = strlen(path);
+       if (!path_len)
                return -1;
 
-       if (gl.gl_pathc > 0)
-               idx = nl80211_readint(gl.gl_pathv[0]);
+       d = opendir("/sys/class/ieee80211");
+       if (!d)
+               return -1;
 
-       globfree(&gl);
+       while ((e = readdir(d)) != NULL) {
+               cur_path = nl80211_phy_path_str(e->d_name);
+               if (!cur_path)
+                       continue;
+
+               cur_path_len = strlen(cur_path);
+               if (cur_path_len < path_len)
+                       continue;
+
+               if (strcmp(cur_path + cur_path_len - path_len, path) != 0)
+                       continue;
+
+               snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", e->d_name);
+               idx = nl80211_readint(buf);
+
+               if (idx >= 0)
+                       break;
+       }
+
+       closedir(d);
 
        return idx;
 }
 
-static int nl80211_phy_idx_from_uci_macaddr(struct uci_section *s)
+static int nl80211_phy_idx_from_macaddr(const char *opt)
 {
-       const char *opt;
        char buf[128];
        int i, idx = -1;
        glob_t gl;
 
-       opt = uci_lookup_option_string(uci_ctx, s, "macaddr");
        if (!opt)
                return -1;
 
-       snprintf(buf, sizeof(buf), "/sys/class/ieee80211/*", opt);      /**/
+       snprintf(buf, sizeof(buf), "/sys/class/ieee80211/*");
        if (glob(buf, 0, NULL, &gl))
                return -1;
 
@@ -289,12 +390,10 @@ static int nl80211_phy_idx_from_uci_macaddr(struct uci_section *s)
        return idx;
 }
 
-static int nl80211_phy_idx_from_uci_phy(struct uci_section *s)
+static int nl80211_phy_idx_from_phy(const char *opt)
 {
-       const char *opt;
        char buf[128];
 
-       opt = uci_lookup_option_string(uci_ctx, s, "phy");
        if (!opt)
                return -1;
 
@@ -305,29 +404,45 @@ static int nl80211_phy_idx_from_uci_phy(struct uci_section *s)
 static int nl80211_phy_idx_from_uci(const char *name)
 {
        struct uci_section *s;
+       const char *opt;
        int idx = -1;
 
        s = iwinfo_uci_get_radio(name, "mac80211");
        if (!s)
-               goto free;
+               goto out;
 
-       idx = nl80211_phy_idx_from_uci_path(s);
+       opt = uci_lookup_option_string(uci_ctx, s, "path");
+       idx = nl80211_phy_idx_from_path(opt);
+       if (idx >= 0)
+               goto out;
 
-       if (idx < 0)
-               idx = nl80211_phy_idx_from_uci_macaddr(s);
+       opt = uci_lookup_option_string(uci_ctx, s, "macaddr");
+       idx = nl80211_phy_idx_from_macaddr(opt);
+       if (idx >= 0)
+               goto out;
 
-       if (idx < 0)
-               idx = nl80211_phy_idx_from_uci_phy(s);
+       opt = uci_lookup_option_string(uci_ctx, s, "phy");
+       idx = nl80211_phy_idx_from_phy(opt);
 
-free:
+out:
        iwinfo_uci_free();
        return idx;
 }
 
+static bool nl80211_is_ifname(const char *name)
+{
+       struct stat st;
+       char buffer[PATH_MAX];
+
+       snprintf(buffer, sizeof(buffer), "/sys/class/net/%s", name);
+       return !lstat(buffer, &st);
+}
+
 static struct nl80211_msg_conveyor * nl80211_msg(const char *ifname,
                                                  int cmd, int flags)
 {
-       int ifidx = -1, phyidx = -1;
+       unsigned int ifidx = 0;
+       int phyidx = -1;
        struct nl80211_msg_conveyor *cv;
 
        if (ifname == NULL)
@@ -336,14 +451,16 @@ static struct nl80211_msg_conveyor * nl80211_msg(const char *ifname,
        if (nl80211_init() < 0)
                return NULL;
 
-       if (!strncmp(ifname, "phy", 3))
-               phyidx = atoi(&ifname[3]);
-       else if (!strncmp(ifname, "radio", 5))
-               phyidx = nl80211_phy_idx_from_uci(ifname);
-       else if (!strncmp(ifname, "mon.", 4))
+       if (!strncmp(ifname, "mon.", 4))
                ifidx = if_nametoindex(&ifname[4]);
-       else
+       else if (nl80211_is_ifname(ifname))
                ifidx = if_nametoindex(ifname);
+       else
+       {
+               phyidx = nl80211_phy_idx_from_phy(ifname);
+               if (phyidx < 0)
+                       phyidx = nl80211_phy_idx_from_uci(ifname);
+       }
 
        /* Valid ifidx must be greater than 0 */
        if ((ifidx <= 0) && (phyidx < 0))
@@ -353,10 +470,9 @@ static struct nl80211_msg_conveyor * nl80211_msg(const char *ifname,
        if (!cv)
                return NULL;
 
-       if (ifidx > -1)
+       if (ifidx > 0)
                NLA_PUT_U32(cv->msg, NL80211_ATTR_IFINDEX, ifidx);
-
-       if (phyidx > -1)
+       else if (phyidx > -1)
                NLA_PUT_U32(cv->msg, NL80211_ATTR_WIPHY, phyidx);
 
        return cv;
@@ -366,20 +482,24 @@ nla_put_failure:
        return NULL;
 }
 
-static struct nl80211_msg_conveyor * nl80211_send(
-       struct nl80211_msg_conveyor *cv,
-       int (*cb_func)(struct nl_msg *, void *), void *cb_arg
-{
+static int nl80211_send(struct nl80211_msg_conveyor *cv,
+                        int (*cb_func)(struct nl_msg *, void *),
+                        void *cb_arg)
+{
        static struct nl80211_msg_conveyor rcv;
-       int err = 1;
+       int err;
 
        if (cb_func)
                nl_cb_set(cv->cb, NL_CB_VALID, NL_CB_CUSTOM, cb_func, cb_arg);
        else
                nl_cb_set(cv->cb, NL_CB_VALID, NL_CB_CUSTOM, nl80211_msg_response, &rcv);
 
-       if (nl_send_auto_complete(nls->nl_sock, cv->msg) < 0)
-               goto err;
+       err = nl_send_auto_complete(nls->nl_sock, cv->msg);
+
+       if (err < 0)
+               goto out;
+
+       err = 1;
 
        nl_cb_err(cv->cb,               NL_CB_CUSTOM, nl80211_msg_error,  &err);
        nl_cb_set(cv->cb, NL_CB_FINISH, NL_CB_CUSTOM, nl80211_msg_finish, &err);
@@ -388,13 +508,23 @@ static struct nl80211_msg_conveyor * nl80211_send(
        while (err > 0)
                nl_recvmsgs(nls->nl_sock, cv->cb);
 
-       return &rcv;
+out:
+       nl80211_free(cv);
+       return err;
+}
+
+static int nl80211_request(const char *ifname, int cmd, int flags,
+                           int (*cb_func)(struct nl_msg *, void *),
+                           void *cb_arg)
+{
+       struct nl80211_msg_conveyor *cv;
 
-err:
-       nl_cb_put(cv->cb);
-       nlmsg_free(cv->msg);
+       cv = nl80211_msg(ifname, cmd, flags);
 
-       return NULL;
+       if (!cv)
+               return -ENOMEM;
+
+       return nl80211_send(cv, cb_func, cb_arg);
 }
 
 static struct nlattr ** nl80211_parse(struct nl_msg *msg)
@@ -408,6 +538,30 @@ static struct nlattr ** nl80211_parse(struct nl_msg *msg)
        return attr;
 }
 
+static int nl80211_get_protocol_features_cb(struct nl_msg *msg, void *arg)
+{
+       uint32_t *features = arg;
+       struct nlattr **attr = nl80211_parse(msg);
+
+       if (attr[NL80211_ATTR_PROTOCOL_FEATURES])
+               *features = nla_get_u32(attr[NL80211_ATTR_PROTOCOL_FEATURES]);
+
+       return NL_SKIP;
+}
+
+static int nl80211_get_protocol_features(const char *ifname)
+{
+       struct nl80211_msg_conveyor *req;
+       uint32_t features = 0;
+
+       req = nl80211_msg(ifname, NL80211_CMD_GET_PROTOCOL_FEATURES, 0);
+       if (req) {
+               nl80211_send(req, nl80211_get_protocol_features_cb, &features);
+               nl80211_free(req);
+       }
+
+       return features;
+}
 
 static int nl80211_subscribe_cb(struct nl_msg *msg, void *arg)
 {
@@ -443,18 +597,24 @@ static int nl80211_subscribe(const char *family, const char *group)
 {
        struct nl80211_group_conveyor cv = { .name = group, .id = -ENOENT };
        struct nl80211_msg_conveyor *req;
+       int err;
 
        req = nl80211_ctl(CTRL_CMD_GETFAMILY, 0);
        if (req)
        {
                NLA_PUT_STRING(req->msg, CTRL_ATTR_FAMILY_NAME, family);
-               nl80211_send(req, nl80211_subscribe_cb, &cv);
+               err = nl80211_send(req, nl80211_subscribe_cb, &cv);
+
+               if (err)
+                       return err;
+
+               return nl_socket_add_membership(nls->nl_sock, cv.id);
 
 nla_put_failure:
                nl80211_free(req);
        }
 
-       return nl_socket_add_membership(nls->nl_sock, cv.id);
+       return -ENOMEM;
 }
 
 
@@ -463,7 +623,7 @@ static int nl80211_wait_cb(struct nl_msg *msg, void *arg)
        struct nl80211_event_conveyor *cv = arg;
        struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
 
-       if (gnlh->cmd == cv->wait)
+       if (cv->wait[gnlh->cmd / 32] & (1 << (gnlh->cmd % 32)))
                cv->recv = gnlh->cmd;
 
        return NL_SKIP;
@@ -474,10 +634,13 @@ static int nl80211_wait_seq_check(struct nl_msg *msg, void *arg)
        return NL_OK;
 }
 
-static int nl80211_wait(const char *family, const char *group, int cmd)
+static int __nl80211_wait(const char *family, const char *group, ...)
 {
-       struct nl80211_event_conveyor cv = { .wait = cmd };
+       struct nl80211_event_conveyor cv = { };
        struct nl_cb *cb;
+       int err = 0;
+       int cmd;
+       va_list ap;
 
        if (nl80211_subscribe(family, group))
                return -ENOENT;
@@ -487,18 +650,35 @@ static int nl80211_wait(const char *family, const char *group, int cmd)
        if (!cb)
                return -ENOMEM;
 
+       nl_cb_err(cb,                  NL_CB_CUSTOM, nl80211_msg_error,      &err);
        nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl80211_wait_seq_check, NULL);
        nl_cb_set(cb, NL_CB_VALID,     NL_CB_CUSTOM, nl80211_wait_cb,        &cv );
 
-       while (!cv.recv)
+       va_start(ap, group);
+
+       for (cmd = va_arg(ap, int); cmd != 0; cmd = va_arg(ap, int))
+               cv.wait[cmd / 32] |= (1 << (cmd % 32));
+
+       va_end(ap);
+
+       while (!cv.recv && !err)
                nl_recvmsgs(nls->nl_sock, cb);
 
        nl_cb_put(cb);
 
-       return 0;
+       return err;
 }
 
+#define nl80211_wait(family, group, ...) \
+       __nl80211_wait(family, group, __VA_ARGS__, 0)
 
+/* This is linux's ieee80211_freq_khz_to_channel() which is:
+ * SPDX-License-Identifier: GPL-2.0
+ * Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2013-2014 Intel Mobile Communications GmbH
+ * Copyright 2017 Intel Deutschland GmbH
+ * Copyright (C) 2018-2022 Intel Corporation
+ */
 static int nl80211_freq2channel(int freq)
 {
        if (freq == 2484)
@@ -507,12 +687,31 @@ static int nl80211_freq2channel(int freq)
                return (freq - 2407) / 5;
        else if (freq >= 4910 && freq <= 4980)
                return (freq - 4000) / 5;
-       else
+       else if (freq < 5925)
                return (freq - 5000) / 5;
+       else if (freq == 5935)
+               return 2;
+       else if (freq <= 45000) /* DMG band lower limit */
+               /* see 802.11ax D6.1 27.3.22.2 */
+               return (freq - 5950) / 5;
+       else if (freq >= 58320 && freq <= 70200)
+               return (freq - 56160) / 2160;
+       else
+               return 0;
 }
 
-static int nl80211_channel2freq(int channel, const char *band)
+/* This is linux's ieee80211_channel_to_freq_khz() which is:
+ * SPDX-License-Identifier: GPL-2.0
+ * Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2013-2014 Intel Mobile Communications GmbH
+ * Copyright 2017 Intel Deutschland GmbH
+ * Copyright (C) 2018-2022 Intel Corporation
+ */
+static int nl80211_channel2freq(int channel, const char *band, bool ax)
 {
+       if (channel < 1)
+               return 0;
+
        if (!band || band[0] != 'a')
        {
                if (channel == 14)
@@ -520,6 +719,18 @@ static int nl80211_channel2freq(int channel, const char *band)
                else if (channel < 14)
                        return (channel * 5) + 2407;
        }
+       else if (strcmp(band, "ad")  == 0)
+       {
+               if (channel < 7)
+                       return 56160 + 2160 * channel;
+       }
+       else if (ax)
+       {
+               if (channel == 2)
+                       return 5935;
+               if (channel < 233)
+                       return (channel * 5) + 5950;
+       }
        else
        {
                if (channel >= 182 && channel <= 196)
@@ -531,55 +742,21 @@ static int nl80211_channel2freq(int channel, const char *band)
        return 0;
 }
 
-static char * nl80211_getval(const char *ifname, const char *buf, const char *key)
+static uint8_t nl80211_freq2band(int freq)
 {
-       int i, len;
-       char lkey[64] = { 0 };
-       const char *ln = buf;
-       static char lval[256] = { 0 };
+       if (freq >= 2412 && freq <= 2484)
+               return IWINFO_BAND_24;
+       else if (freq >= 5160 && freq <= 5885)
+               return IWINFO_BAND_5;
+       else if (freq >= 5925 && freq <= 7125)
+               return IWINFO_BAND_6;
+       else if (freq >= 58320 && freq <= 69120)
+               return IWINFO_BAND_60;
 
-       int matched_if = ifname ? 0 : 1;
-
-
-       for( i = 0, len = strlen(buf); i < len; i++ )
-       {
-               if (!lkey[0] && (buf[i] == ' ' || buf[i] == '\t'))
-               {
-                       ln++;
-               }
-               else if (!lkey[0] && (buf[i] == '='))
-               {
-                       if ((&buf[i] - ln) > 0)
-                               memcpy(lkey, ln, min(sizeof(lkey) - 1, &buf[i] - ln));
-               }
-               else if (buf[i] == '\n')
-               {
-                       if (lkey[0])
-                       {
-                               memcpy(lval, ln + strlen(lkey) + 1,
-                                       min(sizeof(lval) - 1, &buf[i] - ln - strlen(lkey) - 1));
-
-                               if ((ifname != NULL) &&
-                                   (!strcmp(lkey, "interface") || !strcmp(lkey, "bss")) )
-                               {
-                                       matched_if = !strcmp(lval, ifname);
-                               }
-                               else if (matched_if && !strcmp(lkey, key))
-                               {
-                                       return lval;
-                               }
-                       }
-
-                       ln = &buf[i+1];
-                       memset(lkey, 0, sizeof(lkey));
-                       memset(lval, 0, sizeof(lval));
-               }
-       }
-
-       return NULL;
+       return 0;
 }
 
-static int nl80211_ifname2phy_cb(struct nl_msg *msg, void *arg)
+static int nl80211_phyname_cb(struct nl_msg *msg, void *arg)
 {
        char *buf = arg;
        struct nlattr **attr = nl80211_parse(msg);
@@ -596,66 +773,88 @@ static int nl80211_ifname2phy_cb(struct nl_msg *msg, void *arg)
 static char * nl80211_ifname2phy(const char *ifname)
 {
        static char phy[32] = { 0 };
-       struct nl80211_msg_conveyor *req;
 
        memset(phy, 0, sizeof(phy));
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
-       if (req)
-       {
-               nl80211_send(req, nl80211_ifname2phy_cb, phy);
-               nl80211_free(req);
-       }
+       nl80211_request(ifname, NL80211_CMD_GET_WIPHY, 0,
+                       nl80211_phyname_cb, phy);
+
+       return phy[0] ? phy : NULL;
+}
+
+static char * nl80211_phyidx2name(unsigned int idx)
+{
+       struct nl80211_msg_conveyor *cv;
+       static char phy[32] = { 0 };
+
+       if (nl80211_init() < 0)
+               return NULL;
+
+       cv = nl80211_new(nls->nl80211, NL80211_CMD_GET_WIPHY, 0);
+       if (!cv)
+               return NULL;
+
+       NLA_PUT_U32(cv->msg, NL80211_ATTR_WIPHY, idx);
+
+       memset(phy, 0, sizeof(phy));
+       nl80211_send(cv, nl80211_phyname_cb, phy);
 
        return phy[0] ? phy : NULL;
+
+nla_put_failure:
+       return NULL;
 }
 
 static char * nl80211_phy2ifname(const char *ifname)
 {
-       int fd, ifidx = -1, cifidx = -1, phyidx = -1;
-       char buffer[64];
+       int ifidx = -1, cifidx, lmode = 1, clmode, phyidx;
+       char buffer[512];
        static char nif[IFNAMSIZ] = { 0 };
-
        DIR *d;
        struct dirent *e;
 
-       /* Only accept phy name of the form phy%d or radio%d */
+       /* Only accept phy name in the form of phy%d or radio%d */
        if (!ifname)
                return NULL;
-       else if (!strncmp(ifname, "phy", 3))
-               phyidx = atoi(&ifname[3]);
-       else if (!strncmp(ifname, "radio", 5))
-               phyidx = nl80211_phy_idx_from_uci(ifname);
-       else
+
+       phyidx = nl80211_phy_idx_from_phy(ifname);
+       if (phyidx < 0)
+               phyidx = nl80211_phy_idx_from_uci(ifname);;
+       if (phyidx < 0)
                return NULL;
 
        memset(nif, 0, sizeof(nif));
 
-       if (phyidx > -1)
+       if ((d = opendir("/sys/class/net")) != NULL)
        {
-               if ((d = opendir("/sys/class/net")) != NULL)
+               while ((e = readdir(d)) != NULL)
                {
-                       while ((e = readdir(d)) != NULL)
-                       {
-                               snprintf(buffer, sizeof(buffer),
-                                        "/sys/class/net/%s/phy80211/index", e->d_name);
+                       snprintf(buffer, sizeof(buffer),
+                                "/sys/class/net/%s/phy80211/index", e->d_name);
+                       if (nl80211_readint(buffer) != phyidx)
+                               continue;
 
-                               if (nl80211_readint(buffer) == phyidx)
-                               {
-                                       snprintf(buffer, sizeof(buffer),
-                                                "/sys/class/net/%s/ifindex", e->d_name);
+                       snprintf(buffer, sizeof(buffer),
+                                "/sys/class/net/%s/ifindex", e->d_name);
+                       cifidx = nl80211_readint(buffer);
 
-                                       if ((cifidx = nl80211_readint(buffer)) >= 0 &&
-                                           ((ifidx < 0) || (cifidx < ifidx)))
-                                       {
-                                               ifidx = cifidx;
-                                               strncpy(nif, e->d_name, sizeof(nif));
-                                       }
-                               }
-                       }
+                       if (cifidx < 0)
+                               continue;
+
+                       snprintf(buffer, sizeof(buffer),
+                                "/sys/class/net/%s/link_mode", e->d_name);
+                       clmode = nl80211_readint(buffer);
 
-                       closedir(d);
+                       /* prefer non-supplicant-based devices */
+                       if ((ifidx < 0) || (cifidx < ifidx) || ((lmode == 1) && (clmode != 1)))
+                       {
+                               ifidx = cifidx;
+                               lmode = clmode;
+                               strncpy(nif, e->d_name, sizeof(nif) - 1);
+                       }
                }
+
+               closedir(d);
        }
 
        return nif[0] ? nif : NULL;
@@ -671,7 +870,7 @@ static int nl80211_get_mode_cb(struct nl_msg *msg, void *arg)
                IWINFO_OPMODE_CLIENT,           /* managed */
                IWINFO_OPMODE_MASTER,           /* AP */
                IWINFO_OPMODE_AP_VLAN,          /* AP/VLAN */
-               IWINFO_OPMODE_WDS,                      /* WDS */
+               IWINFO_OPMODE_WDS,              /* WDS */
                IWINFO_OPMODE_MONITOR,          /* monitor */
                IWINFO_OPMODE_MESHPOINT,        /* mesh point */
                IWINFO_OPMODE_P2P_CLIENT,       /* P2P-client */
@@ -688,164 +887,266 @@ static int nl80211_get_mode_cb(struct nl_msg *msg, void *arg)
 static int nl80211_get_mode(const char *ifname, int *buf)
 {
        char *res;
-       struct nl80211_msg_conveyor *req;
 
-       res = nl80211_phy2ifname(ifname);
-       req = nl80211_msg(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0);
        *buf = IWINFO_OPMODE_UNKNOWN;
 
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_mode_cb, buf);
-               nl80211_free(req);
-       }
+       res = nl80211_phy2ifname(ifname);
+
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                       nl80211_get_mode_cb, buf);
 
        return (*buf == IWINFO_OPMODE_UNKNOWN) ? -1 : 0;
 }
 
-static char * nl80211_hostapd_info(const char *ifname)
+static int __nl80211_hostapd_query(const char *ifname, ...)
 {
-       int mode;
-       char *phy;
-       char path[32] = { 0 };
-       static char buf[4096] = { 0 };
-       FILE *conf;
+       va_list ap, ap_cur;
+       char *phy, *search, *dest, *key, *val, buf[128];
+       int len, mode, found = 0, match = 1;
+       FILE *fp;
 
        if (nl80211_get_mode(ifname, &mode))
-               return NULL;
+               return 0;
+
+       if (mode != IWINFO_OPMODE_MASTER && mode != IWINFO_OPMODE_AP_VLAN)
+               return 0;
+
+       phy = nl80211_ifname2phy(ifname);
+
+       if (!phy)
+               return 0;
+
+       snprintf(buf, sizeof(buf), "/var/run/hostapd-%s.conf", phy);
+       fp = fopen(buf, "r");
+
+       if (!fp)
+               return 0;
+
+       va_start(ap, ifname);
+
+       /* clear all destination buffers */
+       va_copy(ap_cur, ap);
+
+       while ((search = va_arg(ap_cur, char *)) != NULL)
+       {
+               dest = va_arg(ap_cur, char *);
+               len  = va_arg(ap_cur, int);
+
+               memset(dest, 0, len);
+       }
+
+       va_end(ap_cur);
 
-       if ((mode == IWINFO_OPMODE_MASTER || mode == IWINFO_OPMODE_AP_VLAN) &&
-           (phy = nl80211_ifname2phy(ifname)) != NULL)
+       /* iterate applicable lines and copy found values into dest buffers */
+       while (fgets(buf, sizeof(buf), fp))
        {
-               snprintf(path, sizeof(path), "/var/run/hostapd-%s.conf", phy);
+               key = strtok(buf, " =\t\n");
+               val = strtok(NULL, "\n");
 
-               if ((conf = fopen(path, "r")) != NULL)
+               if (!key || !val || !*key || *key == '#')
+                       continue;
+
+               if (!strcmp(key, "interface") || !strcmp(key, "bss"))
+                       match = !strcmp(ifname, val);
+
+               if (!match)
+                       continue;
+
+               va_copy(ap_cur, ap);
+
+               while ((search = va_arg(ap_cur, char *)) != NULL)
                {
-                       fread(buf, sizeof(buf) - 1, 1, conf);
-                       fclose(conf);
+                       dest = va_arg(ap_cur, char *);
+                       len  = va_arg(ap_cur, int);
 
-                       return buf;
+                       if (!strcmp(search, key))
+                       {
+                               strncpy(dest, val, len - 1);
+                               found++;
+                               break;
+                       }
                }
+
+               va_end(ap_cur);
        }
 
-       return NULL;
+       fclose(fp);
+
+       va_end(ap);
+
+       return found;
 }
 
+#define nl80211_hostapd_query(ifname, ...) \
+       __nl80211_hostapd_query(ifname, ##__VA_ARGS__, NULL)
+
+
 static inline int nl80211_wpactl_recv(int sock, char *buf, int blen)
 {
        fd_set rfds;
-       struct timeval tv = { 2, 0 };
+       struct timeval tv = { 0, 256000 };
 
        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);
 
        memset(buf, 0, blen);
 
-
        if (select(sock + 1, &rfds, NULL, NULL, &tv) < 0)
                return -1;
 
        if (!FD_ISSET(sock, &rfds))
                return -1;
 
-       return recv(sock, buf, blen, 0);
+       return recv(sock, buf, blen - 1, 0);
 }
 
-static char * nl80211_wpactl_info(const char *ifname, const char *cmd,
-                                                                  const char *event)
+static int nl80211_wpactl_connect(const char *ifname, struct sockaddr_un *local)
 {
-       int numtry = 0;
-       int sock = -1;
-       char *rv = NULL;
-       size_t remote_length, local_length;
-       static char buffer[10240] = { 0 };
-
-       struct sockaddr_un local = { 0 };
        struct sockaddr_un remote = { 0 };
+       size_t remote_length, local_length;
 
-
-       sock = socket(PF_UNIX, SOCK_DGRAM, 0);
+       int sock = socket(PF_UNIX, SOCK_DGRAM, 0);
        if (sock < 0)
-               return NULL;
+               return sock;
 
        remote.sun_family = AF_UNIX;
-       remote_length = sizeof(remote.sun_family) + sprintf(remote.sun_path,
-               "/var/run/wpa_supplicant-%s/%s", ifname, ifname);
+       remote_length = sizeof(remote.sun_family) +
+               sprintf(remote.sun_path, "/var/run/wpa_supplicant/%s", ifname);
+
+       /* Set client socket file permissions so that bind() creates the client
+       * socket with these permissions and there is no need to try to change
+       * them with chmod() after bind() which would have potential issues with
+       * race conditions. These permissions are needed to make sure the server
+       * side (wpa_supplicant or hostapd) can reply to the control interface
+       * messages.
+       *
+       * The lchown() calls below after bind() are also part of the needed
+       * operations to allow the response to go through. Those are using the
+       * no-deference-symlinks version to avoid races. */
+       fchmod(sock, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
 
        if (fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC) < 0)
-               goto out;
-
-       if (connect(sock, (struct sockaddr *) &remote, remote_length))
        {
-               remote_length = sizeof(remote.sun_family) + sprintf(remote.sun_path,
-                       "/var/run/wpa_supplicant/%s", ifname);
-
-               if (connect(sock, (struct sockaddr *) &remote, remote_length))
-                       goto out;
+               close(sock);
+               return -1;
        }
 
-       local.sun_family = AF_UNIX;
-       local_length = sizeof(local.sun_family) +
-               sprintf(local.sun_path, "/var/run/iwinfo-%s-%d", ifname, getpid());
-
-       if (bind(sock, (struct sockaddr *) &local, local_length))
-               goto out;
-
-
-       if (event)
+       if (connect(sock, (struct sockaddr *)&remote, remote_length))
        {
-               send(sock, "ATTACH", 6, 0);
-
-               if (nl80211_wpactl_recv(sock, buffer, sizeof(buffer)-1) <= 0)
-                       goto out;
+               close(sock);
+               return -1;
        }
 
+       local->sun_family = AF_UNIX;
+       local_length = sizeof(local->sun_family) +
+               sprintf(local->sun_path, "/var/run/iwinfo-%s-%d", ifname, getpid());
 
-       send(sock, cmd, strlen(cmd), 0);
-
-       /* we might have to scan up to 72 channels / 256ms per channel */
-       /* this makes up to 18.5s hence 10 tries */
-       while( numtry++ < 10 )
+       if (bind(sock, (struct sockaddr *)local, local_length) < 0)
        {
-               char *bracket;
+               close(sock);
+               return -1;
+       }
 
-               /* make sure there is a terminating nul byte */
-               if (nl80211_wpactl_recv(sock, buffer, sizeof(buffer)-1) <= 0)
-               {
-                       if (event)
-                               continue;
+       /* Set group even if we do not have privileges to change owner */
+       lchown(local->sun_path, -1, 101);
+       lchown(local->sun_path, 101, 101);
 
-                       break;
-               }
+       return sock;
+}
 
-               if ((!event && buffer[0] != '<') || (event && strstr(buffer, event)))
+static int __nl80211_wpactl_query(const char *ifname, ...)
+{
+       va_list ap, ap_cur;
+       struct sockaddr_un local = { 0 };
+       int len, mode, found = 0, sock = -1;
+       char *search, *dest, *key, *val, *line, *pos, buf[512];
+
+       if (nl80211_get_mode(ifname, &mode))
+               return 0;
+
+       if (mode != IWINFO_OPMODE_CLIENT &&
+           mode != IWINFO_OPMODE_ADHOC &&
+           mode != IWINFO_OPMODE_MESHPOINT)
+               return 0;
+
+       sock = nl80211_wpactl_connect(ifname, &local);
+
+       if (sock < 0)
+               return 0;
+
+       va_start(ap, ifname);
+
+       /* clear all destination buffers */
+       va_copy(ap_cur, ap);
+
+       while ((search = va_arg(ap_cur, char *)) != NULL)
+       {
+               dest = va_arg(ap_cur, char *);
+               len  = va_arg(ap_cur, int);
+
+               memset(dest, 0, len);
+       }
+
+       va_end(ap_cur);
+
+       send(sock, "STATUS", 6, 0);
+
+       while (true)
+       {
+               if (nl80211_wpactl_recv(sock, buf, sizeof(buf)) <= 0)
                        break;
 
-               /* there may be more than max(numtry) BSS-ADDED events */
-               /* ignore them similar to wpa_cli */
-               if (buffer[0] == '<' &&
-                               (bracket=strchr(buffer,'>')) != NULL &&
-                               strncmp(bracket+1,"CTRL-EVENT-BSS-ADDED",20) == 0)
-                       numtry--;
+               if (buf[0] == '<')
+                       continue;
+
+               for (line = strtok_r(buf, "\n", &pos);
+                        line != NULL;
+                        line = strtok_r(NULL, "\n", &pos))
+               {
+                       key = strtok(line, "=");
+                       val = strtok(NULL, "\n");
+
+                       if (!key || !val)
+                               continue;
+
+                       va_copy(ap_cur, ap);
+
+                       while ((search = va_arg(ap_cur, char *)) != NULL)
+                       {
+                               dest = va_arg(ap_cur, char *);
+                               len  = va_arg(ap_cur, int);
+
+                               if (!strcmp(search, key))
+                               {
+                                       strncpy(dest, val, len - 1);
+                                       found++;
+                                       break;
+                               }
+                       }
+
+                       va_end(ap_cur);
+               }
+
+               break;
        }
 
-       rv = buffer;
+       va_end(ap);
 
-out:
        close(sock);
+       unlink(local.sun_path);
 
-       if (local.sun_family)
-               unlink(local.sun_path);
-
-       return rv;
+       return found;
 }
 
+#define nl80211_wpactl_query(ifname, ...) \
+       __nl80211_wpactl_query(ifname, ##__VA_ARGS__, NULL)
+
+
 static char * nl80211_ifadd(const char *ifname)
 {
-       int phyidx;
-       char *rv = NULL, path[PATH_MAX];
+       char path[PATH_MAX];
        static char nif[IFNAMSIZ] = { 0 };
-       struct nl80211_msg_conveyor *req, *res;
+       struct nl80211_msg_conveyor *req;
        FILE *sysfs;
 
        req = nl80211_msg(ifname, NL80211_CMD_NEW_INTERFACE, 0);
@@ -867,13 +1168,13 @@ static char * nl80211_ifadd(const char *ifname)
                        fclose(sysfs);
                }
 
-               rv = nif;
+               return nif;
 
        nla_put_failure:
                nl80211_free(req);
        }
 
-       return rv;
+       return NULL;
 }
 
 static void nl80211_ifdel(const char *ifname)
@@ -886,6 +1187,7 @@ static void nl80211_ifdel(const char *ifname)
                NLA_PUT_STRING(req->msg, NL80211_ATTR_IFNAME, ifname);
 
                nl80211_send(req, NULL, NULL);
+               return;
 
        nla_put_failure:
                nl80211_free(req);
@@ -901,7 +1203,7 @@ static void nl80211_hostapd_hup(const char *ifname)
        if (phy)
        {
                snprintf(buf, sizeof(buf), "/var/run/wifi-%s.pid", phy);
-               if ((fd = open(buf, O_RDONLY)) > 0)
+               if ((fd = open(buf, O_RDONLY)) >= 0)
                {
                        if (read(fd, buf, sizeof(buf)) > 0)
                                pid = atoi(buf);
@@ -925,6 +1227,20 @@ struct nl80211_ssid_bssid {
        unsigned char bssid[7];
 };
 
+static int nl80211_get_macaddr_cb(struct nl_msg *msg, void *arg)
+{
+       struct nl80211_ssid_bssid *sb = arg;
+       struct nlattr **tb = nl80211_parse(msg);
+
+       if (tb[NL80211_ATTR_MAC]) {
+               sb->bssid[0] = 1;
+               memcpy(sb->bssid + 1, nla_data(tb[NL80211_ATTR_MAC]),
+                      sizeof(sb->bssid) - 1);
+       }
+
+       return NL_SKIP;
+}
+
 static int nl80211_get_ssid_bssid_cb(struct nl_msg *msg, void *arg)
 {
        int ielen;
@@ -933,8 +1249,8 @@ static int nl80211_get_ssid_bssid_cb(struct nl_msg *msg, void *arg)
        struct nlattr **tb = nl80211_parse(msg);
        struct nlattr *bss[NL80211_BSS_MAX + 1];
 
-       static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
-               [NL80211_BSS_INFORMATION_ELEMENTS] = {                 },
+       static const struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+               [NL80211_BSS_INFORMATION_ELEMENTS] = { 0 },
                [NL80211_BSS_STATUS]               = { .type = NLA_U32 },
        };
 
@@ -986,64 +1302,56 @@ static int nl80211_get_ssid_bssid_cb(struct nl_msg *msg, void *arg)
 static int nl80211_get_ssid(const char *ifname, char *buf)
 {
        char *res;
-       struct nl80211_msg_conveyor *req;
-       struct nl80211_ssid_bssid sb;
+       struct nl80211_ssid_bssid sb = { .ssid = (unsigned char *)buf };
 
        /* try to find ssid from scan dump results */
        res = nl80211_phy2ifname(ifname);
-       req = nl80211_msg(res ? res : ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
+       sb.ssid[0] = 0;
 
-       sb.ssid = buf;
-       *buf = 0;
-
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_ssid_bssid_cb, &sb);
-               nl80211_free(req);
-       }
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP,
+                       nl80211_get_ssid_bssid_cb, &sb);
 
        /* failed, try to find from hostapd info */
-       if ((*buf == 0) &&
-           (res = nl80211_hostapd_info(ifname)) &&
-           (res = nl80211_getval(ifname, res, "ssid")))
-       {
-               memcpy(buf, res, strlen(res));
-       }
+       if (sb.ssid[0] == 0)
+               nl80211_hostapd_query(ifname, "ssid", sb.ssid,
+                                     IWINFO_ESSID_MAX_SIZE + 1);
 
-       return (*buf == 0) ? -1 : 0;
+       /* failed, try to obtain Mesh ID */
+       if (sb.ssid[0] == 0)
+               iwinfo_ubus_query(res ? res : ifname, "mesh_id",
+                                 buf, IWINFO_ESSID_MAX_SIZE + 1);
+
+       return (sb.ssid[0] == 0) ? -1 : 0;
 }
 
 static int nl80211_get_bssid(const char *ifname, char *buf)
 {
-       char *res;
-       struct nl80211_msg_conveyor *req;
-       struct nl80211_ssid_bssid sb;
+       char *res, bssid[sizeof("FF:FF:FF:FF:FF:FF\0")];
+       struct nl80211_ssid_bssid sb = { };
 
-       /* try to find bssid from scan dump results */
        res = nl80211_phy2ifname(ifname);
-       req = nl80211_msg(res ? res : ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
 
-       sb.ssid = NULL;
-       sb.bssid[0] = 0;
+       /* try to obtain mac address via NL80211_CMD_GET_INTERFACE */
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                       nl80211_get_macaddr_cb, &sb);
 
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_ssid_bssid_cb, &sb);
-               nl80211_free(req);
-       }
+       /* failed, try to find bssid from scan dump results */
+       if (sb.bssid[0] == 0)
+               nl80211_request(res ? res : ifname,
+                               NL80211_CMD_GET_SCAN, NLM_F_DUMP,
+                               nl80211_get_ssid_bssid_cb, &sb);
 
        /* failed, try to find mac from hostapd info */
        if ((sb.bssid[0] == 0) &&
-           (res = nl80211_hostapd_info(ifname)) &&
-           (res = nl80211_getval(ifname, res, "bssid")))
+           nl80211_hostapd_query(ifname, "bssid", bssid, sizeof(bssid)))
        {
                sb.bssid[0] = 1;
-               sb.bssid[1] = strtol(&res[0],  NULL, 16);
-               sb.bssid[2] = strtol(&res[3],  NULL, 16);
-               sb.bssid[3] = strtol(&res[6],  NULL, 16);
-               sb.bssid[4] = strtol(&res[9],  NULL, 16);
-               sb.bssid[5] = strtol(&res[12], NULL, 16);
-               sb.bssid[6] = strtol(&res[15], NULL, 16);
+               sb.bssid[1] = strtol(&bssid[0],  NULL, 16);
+               sb.bssid[2] = strtol(&bssid[3],  NULL, 16);
+               sb.bssid[3] = strtol(&bssid[6],  NULL, 16);
+               sb.bssid[4] = strtol(&bssid[9],  NULL, 16);
+               sb.bssid[5] = strtol(&bssid[12], NULL, 16);
+               sb.bssid[6] = strtol(&bssid[15], NULL, 16);
        }
 
        if (sb.bssid[0])
@@ -1065,7 +1373,7 @@ static int nl80211_get_frequency_scan_cb(struct nl_msg *msg, void *arg)
        struct nlattr **attr = nl80211_parse(msg);
        struct nlattr *binfo[NL80211_BSS_MAX + 1];
 
-       static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+       static const struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
                [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
                [NL80211_BSS_STATUS]    = { .type = NLA_U32 },
        };
@@ -1094,49 +1402,86 @@ static int nl80211_get_frequency_info_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_frequency(const char *ifname, int *buf)
 {
-       int chn;
-       char *res, *channel;
-       struct nl80211_msg_conveyor *req;
+       char *res, channel[4] = { 0 }, hwmode[3] = { 0 }, ax[2] = { 0 };
 
        /* try to find frequency from interface info */
        res = nl80211_phy2ifname(ifname);
-       req = nl80211_msg(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0);
        *buf = 0;
 
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_frequency_info_cb, buf);
-               nl80211_free(req);
-       }
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                       nl80211_get_frequency_info_cb, buf);
 
        /* failed, try to find frequency from hostapd info */
        if ((*buf == 0) &&
-           (res = nl80211_hostapd_info(ifname)) &&
-           (channel = nl80211_getval(NULL, res, "channel")))
+           nl80211_hostapd_query(ifname, "hw_mode", hwmode, sizeof(hwmode),
+                                         "channel", channel, sizeof(channel),
+                                         "ieee80211ax", ax, sizeof(ax)) >= 2)
        {
-               chn = atoi(channel);
-               *buf = nl80211_channel2freq(chn, nl80211_getval(NULL, res, "hw_mode"));
+               *buf = nl80211_channel2freq(atoi(channel), hwmode, ax[0] == '1');
        }
-       else
+
+       /* failed, try to find frequency from scan results */
+       if (*buf == 0)
        {
-               /* failed, try to find frequency from scan results */
-               if (*buf == 0)
-               {
-                       res = nl80211_phy2ifname(ifname);
-                       req = nl80211_msg(res ? res : ifname, NL80211_CMD_GET_SCAN,
-                                         NLM_F_DUMP);
+               res = nl80211_phy2ifname(ifname);
 
-                       if (req)
-                       {
-                               nl80211_send(req, nl80211_get_frequency_scan_cb, buf);
-                               nl80211_free(req);
-                       }
-               }
+               nl80211_request(res ? res : ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP,
+                               nl80211_get_frequency_scan_cb, buf);
        }
 
        return (*buf == 0) ? -1 : 0;
 }
 
+static int nl80211_get_center_freq1_cb(struct nl_msg *msg, void *arg)
+{
+       int *freq = arg;
+       struct nlattr **tb = nl80211_parse(msg);
+
+       if (tb[NL80211_ATTR_CENTER_FREQ1])
+               *freq = nla_get_u32(tb[NL80211_ATTR_CENTER_FREQ1]);
+
+       return NL_SKIP;
+}
+
+static int nl80211_get_center_freq1(const char *ifname, int *buf)
+{
+       char *res;
+
+       /* try to find frequency from interface info */
+       res = nl80211_phy2ifname(ifname);
+       *buf = 0;
+
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                       nl80211_get_center_freq1_cb, buf);
+
+       return (*buf == 0) ? -1 : 0;
+}
+
+static int nl80211_get_center_freq2_cb(struct nl_msg *msg, void *arg)
+{
+       int *freq = arg;
+       struct nlattr **tb = nl80211_parse(msg);
+
+       if (tb[NL80211_ATTR_CENTER_FREQ2])
+               *freq = nla_get_u32(tb[NL80211_ATTR_CENTER_FREQ2]);
+
+       return NL_SKIP;
+}
+
+static int nl80211_get_center_freq2(const char *ifname, int *buf)
+{
+       char *res;
+
+       /* try to find frequency from interface info */
+       res = nl80211_phy2ifname(ifname);
+       *buf = 0;
+
+       nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                       nl80211_get_center_freq2_cb, buf);
+
+       return (*buf == 0) ? -1 : 0;
+}
+
 static int nl80211_get_channel(const char *ifname, int *buf)
 {
        if (!nl80211_get_frequency(ifname, buf))
@@ -1148,22 +1493,51 @@ static int nl80211_get_channel(const char *ifname, int *buf)
        return -1;
 }
 
+static int nl80211_get_center_chan1(const char *ifname, int *buf)
+{
+       if (!nl80211_get_center_freq1(ifname, buf))
+       {
+               *buf = nl80211_freq2channel(*buf);
+               return 0;
+       }
+
+       return -1;
+}
+
+static int nl80211_get_center_chan2(const char *ifname, int *buf)
+{
+       if (!nl80211_get_center_freq2(ifname, buf))
+       {
+               *buf = nl80211_freq2channel(*buf);
+               return 0;
+       }
+
+       return -1;
+}
+
+static int nl80211_get_txpower_cb(struct nl_msg *msg, void *arg)
+{
+       int *buf = arg;
+       struct nlattr **tb = nl80211_parse(msg);
+
+       if (tb[NL80211_ATTR_WIPHY_TX_POWER_LEVEL])
+               *buf = iwinfo_mbm2dbm(nla_get_u32(tb[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]));
+
+       return NL_SKIP;
+}
 
 static int nl80211_get_txpower(const char *ifname, int *buf)
 {
-#if 0
        char *res;
-       char path[PATH_MAX];
 
-       res = nl80211_ifname2phy(ifname);
-       snprintf(path, sizeof(path), "/sys/kernel/debug/ieee80211/%s/power",
-                res ? res : ifname);
+       res = nl80211_phy2ifname(ifname);
+       *buf = 0;
 
-       if ((*buf = nl80211_readint(path)) > -1)
-               return 0;
-#endif
+       if (nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0,
+                           nl80211_get_txpower_cb, buf))
+               return -1;
 
-       return wext_ops.txpower(ifname, buf);
+       return 0;
 }
 
 
@@ -1176,10 +1550,12 @@ static int nl80211_fill_signal_cb(struct nl_msg *msg, void *arg)
        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] = {
+       static const 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_BYTES64]    = { .type = NLA_U64    },
+               [NL80211_STA_INFO_TX_BYTES64]    = { .type = NLA_U64    },
                [NL80211_STA_INFO_RX_PACKETS]    = { .type = NLA_U32    },
                [NL80211_STA_INFO_TX_PACKETS]    = { .type = NLA_U32    },
                [NL80211_STA_INFO_SIGNAL]        = { .type = NLA_U8     },
@@ -1189,7 +1565,7 @@ static int nl80211_fill_signal_cb(struct nl_msg *msg, void *arg)
                [NL80211_STA_INFO_PLINK_STATE]   = { .type = NLA_U8     },
        };
 
-       static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
+       static const 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 },
@@ -1204,7 +1580,8 @@ static int nl80211_fill_signal_cb(struct nl_msg *msg, void *arg)
                        if (sinfo[NL80211_STA_INFO_SIGNAL])
                        {
                                dbm = nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]);
-                               rr->rssi = rr->rssi ? (int8_t)((rr->rssi + dbm) / 2) : dbm;
+                               rr->rssi = (rr->rssi * rr->rssi_samples + dbm) / (rr->rssi_samples + 1);
+                               rr->rssi_samples++;
                        }
 
                        if (sinfo[NL80211_STA_INFO_TX_BITRATE])
@@ -1216,8 +1593,8 @@ static int nl80211_fill_signal_cb(struct nl_msg *msg, void *arg)
                                        if (rinfo[NL80211_RATE_INFO_BITRATE])
                                        {
                                                mbit = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]);
-                                               rr->rate = rr->rate
-                                                       ? (int16_t)((rr->rate + mbit) / 2) : mbit;
+                                               rr->rate = (rr->rate * rr->rate_samples + mbit) / (rr->rate_samples + 1);
+                                               rr->rate_samples++;
                                        }
                                }
                        }
@@ -1231,10 +1608,8 @@ static void nl80211_fill_signal(const char *ifname, struct nl80211_rssi_rate *r)
 {
        DIR *d;
        struct dirent *de;
-       struct nl80211_msg_conveyor *req;
 
-       r->rssi = 0;
-       r->rate = 0;
+       memset(r, 0, sizeof(*r));
 
        if ((d = opendir("/sys/class/net")) != NULL)
        {
@@ -1244,14 +1619,8 @@ static void nl80211_fill_signal(const char *ifname, struct nl80211_rssi_rate *r)
                            (!de->d_name[strlen(ifname)] ||
                             !strncmp(&de->d_name[strlen(ifname)], ".sta", 4)))
                        {
-                               req = nl80211_msg(de->d_name, NL80211_CMD_GET_STATION,
-                                                 NLM_F_DUMP);
-
-                               if (req)
-                               {
-                                       nl80211_send(req, nl80211_fill_signal_cb, r);
-                                       nl80211_free(req);
-                               }
+                               nl80211_request(de->d_name, NL80211_CMD_GET_STATION,
+                                               NLM_F_DUMP, nl80211_fill_signal_cb, r);
                        }
                }
 
@@ -1265,7 +1634,7 @@ static int nl80211_get_bitrate(const char *ifname, int *buf)
 
        nl80211_fill_signal(ifname, &rr);
 
-       if (rr.rate)
+       if (rr.rate_samples)
        {
                *buf = (rr.rate * 100);
                return 0;
@@ -1280,7 +1649,7 @@ static int nl80211_get_signal(const char *ifname, int *buf)
 
        nl80211_fill_signal(ifname, &rr);
 
-       if (rr.rssi)
+       if (rr.rssi_samples)
        {
                *buf = rr.rssi;
                return 0;
@@ -1295,7 +1664,7 @@ static int nl80211_get_noise_cb(struct nl_msg *msg, void *arg)
        struct nlattr **tb = nl80211_parse(msg);
        struct nlattr *si[NL80211_SURVEY_INFO_MAX + 1];
 
-       static struct nla_policy sp[NL80211_SURVEY_INFO_MAX + 1] = {
+       static const struct nla_policy sp[NL80211_SURVEY_INFO_MAX + 1] = {
                [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 },
                [NL80211_SURVEY_INFO_NOISE]     = { .type = NLA_U8  },
        };
@@ -1319,24 +1688,17 @@ static int nl80211_get_noise_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_noise(const char *ifname, int *buf)
 {
-       int8_t noise;
-       struct nl80211_msg_conveyor *req;
-
-       req = nl80211_msg(ifname, NL80211_CMD_GET_SURVEY, NLM_F_DUMP);
-       if (req)
-       {
-               noise = 0;
+       int8_t noise = 0;
 
-               nl80211_send(req, nl80211_get_noise_cb, &noise);
-               nl80211_free(req);
+       if (nl80211_request(ifname, NL80211_CMD_GET_SURVEY, NLM_F_DUMP,
+                           nl80211_get_noise_cb, &noise))
+               goto out;
 
-               if (noise)
-               {
-                       *buf = noise;
-                       return 0;
-               }
-       }
+       *buf = noise;
+       return 0;
 
+out:
+       *buf = 0;
        return -1;
 }
 
@@ -1381,166 +1743,273 @@ static int nl80211_get_quality_max(const char *ifname, int *buf)
        return 0;
 }
 
-static int nl80211_get_encryption(const char *ifname, char *buf)
+static int nl80211_check_wepkey(const char *key)
 {
-       int i;
-       char k[9];
-       char *val, *res;
-       struct iwinfo_crypto_entry *c = (struct iwinfo_crypto_entry *)buf;
-
-       /* WPA supplicant */
-       if ((res = nl80211_wpactl_info(ifname, "STATUS", NULL)) &&
-           (val = nl80211_getval(NULL, res, "pairwise_cipher")))
+       if (key && *key)
        {
-               /* WEP */
-               if (strstr(val, "WEP"))
+               switch (strlen(key))
                {
-                       if (strstr(val, "WEP-40"))
-                               c->pair_ciphers |= IWINFO_CIPHER_WEP40;
-
-                       else if (strstr(val, "WEP-104"))
-                               c->pair_ciphers |= IWINFO_CIPHER_WEP104;
-
-                       c->enabled       = 1;
-                       c->group_ciphers = c->pair_ciphers;
+               case 5:
+               case 10:
+                       return IWINFO_CIPHER_WEP40;
 
-                       c->auth_suites |= IWINFO_KMGMT_NONE;
-                       c->auth_algs   |= IWINFO_AUTH_OPEN; /* XXX: assumption */
+               case 13:
+               case 26:
+                       return IWINFO_CIPHER_WEP104;
                }
+       }
 
-               /* WPA */
-               else
-               {
-                       if (strstr(val, "TKIP"))
-                               c->pair_ciphers |= IWINFO_CIPHER_TKIP;
-
-                       else if (strstr(val, "CCMP"))
-                               c->pair_ciphers |= IWINFO_CIPHER_CCMP;
+       return 0;
+}
 
-                       else if (strstr(val, "NONE"))
-                               c->pair_ciphers |= IWINFO_CIPHER_NONE;
+static const struct {
+       const char *match;
+       int version;
+       int suite;
+} wpa_key_mgmt_strings[] = {
+       { "IEEE 802.1X/EAP", 0, IWINFO_KMGMT_8021x },
+       { "EAP-SUITE-B-192", 4, IWINFO_KMGMT_8021x },
+       { "EAP-SUITE-B",     4, IWINFO_KMGMT_8021x },
+       { "EAP-SHA384",      4, IWINFO_KMGMT_8021x },
+       { "EAP-SHA256",      0, IWINFO_KMGMT_8021x },
+       { "PSK-SHA256",      0, IWINFO_KMGMT_PSK },
+       { "NONE",            0, IWINFO_KMGMT_NONE },
+       { "None",            0, IWINFO_KMGMT_NONE },
+       { "PSK",             0, IWINFO_KMGMT_PSK },
+       { "EAP",             0, IWINFO_KMGMT_8021x },
+       { "SAE",             4, IWINFO_KMGMT_SAE },
+       { "OWE",             4, IWINFO_KMGMT_OWE }
+};
 
-                       else if (strstr(val, "WEP-40"))
-                               c->pair_ciphers |= IWINFO_CIPHER_WEP40;
+static void parse_wpa_suites(const char *str, int defversion,
+                             uint8_t *versions, uint8_t *suites)
+{
+       size_t l;
+       int i, version;
+       const char *p, *q, *m, *sep = " \t\n,-+/";
 
-                       else if (strstr(val, "WEP-104"))
-                               c->pair_ciphers |= IWINFO_CIPHER_WEP104;
+       for (p = str; *p; )
+       {
+               q = p;
 
+               for (i = 0; i < ARRAY_SIZE(wpa_key_mgmt_strings); i++)
+               {
+                       m = wpa_key_mgmt_strings[i].match;
+                       l = strlen(m);
 
-                       if ((val = nl80211_getval(NULL, res, "group_cipher")))
+                       if (!strncmp(q, m, l) && (!q[l] || strchr(sep, q[l])))
                        {
-                               if (strstr(val, "TKIP"))
-                                       c->group_ciphers |= IWINFO_CIPHER_TKIP;
-
-                               else if (strstr(val, "CCMP"))
-                                       c->group_ciphers |= IWINFO_CIPHER_CCMP;
-
-                               else if (strstr(val, "NONE"))
-                                       c->group_ciphers |= IWINFO_CIPHER_NONE;
+                               if (wpa_key_mgmt_strings[i].version != 0)
+                                       version = wpa_key_mgmt_strings[i].version;
+                               else
+                                       version = defversion;
 
-                               else if (strstr(val, "WEP-40"))
-                                       c->group_ciphers |= IWINFO_CIPHER_WEP40;
+                               *versions |= version;
+                               *suites |= wpa_key_mgmt_strings[i].suite;
 
-                               else if (strstr(val, "WEP-104"))
-                                       c->group_ciphers |= IWINFO_CIPHER_WEP104;
+                               q += l;
+                               break;
                        }
+               }
 
+               if (q == p)
+                       q += strcspn(q, sep);
 
-                       if ((val = nl80211_getval(NULL, res, "key_mgmt")))
-                       {
-                               if (strstr(val, "WPA2"))
-                                       c->wpa_version = 2;
+               p = q + strspn(q, sep);
+       }
+}
+
+static const struct {
+       const char *match;
+       int cipher;
+} wpa_cipher_strings[] = {
+       { "WEP-104", IWINFO_CIPHER_WEP104  },
+       { "WEP-40",  IWINFO_CIPHER_WEP40   },
+       { "NONE",    IWINFO_CIPHER_NONE    },
+       { "TKIP",    IWINFO_CIPHER_TKIP    },
+       { "CCMP-256",IWINFO_CIPHER_CCMP256 },
+       { "CCMP",    IWINFO_CIPHER_CCMP    },
+       { "GCMP-256",IWINFO_CIPHER_GCMP256 },
+       { "GCMP",    IWINFO_CIPHER_GCMP    }
+};
 
-                               else if (strstr(val, "WPA"))
-                                       c->wpa_version = 1;
+static void parse_wpa_ciphers(const char *str, uint16_t *ciphers)
+{
+       int i;
+       size_t l;
+       const char *m, *p, *q, *sep = " \t\n,-+/";
 
+       for (p = str; *p; )
+       {
+               q = p;
 
-                               if (strstr(val, "PSK"))
-                                       c->auth_suites |= IWINFO_KMGMT_PSK;
+               for (i = 0; i < ARRAY_SIZE(wpa_cipher_strings); i++)
+               {
+                       m = wpa_cipher_strings[i].match;
+                       l = strlen(m);
 
-                               else if (strstr(val, "EAP") || strstr(val, "802.1X"))
-                                       c->auth_suites |= IWINFO_KMGMT_8021x;
+                       if (!strncmp(q, m, l) && (!q[l] || strchr(sep, q[l])))
+                       {
+                               *ciphers |= wpa_cipher_strings[i].cipher;
 
-                               else if (strstr(val, "NONE"))
-                                       c->auth_suites |= IWINFO_KMGMT_NONE;
+                               q += l;
+                               break;
                        }
-
-                       c->enabled = (c->wpa_version && c->auth_suites) ? 1 : 0;
                }
 
-               return 0;
+               if (q == p)
+                       q += strcspn(q, sep);
+
+               p = q + strspn(q, sep);
        }
+}
 
-       /* Hostapd */
-       else if ((res = nl80211_hostapd_info(ifname)))
-       {
-               if ((val = nl80211_getval(ifname, res, "wpa")) != NULL)
-                       c->wpa_version = atoi(val);
+static int nl80211_get_encryption(const char *ifname, char *buf)
+{
+       char *p;
+       int opmode;
+       uint8_t wpa_version = 0;
+       char wpa[2], wpa_key_mgmt[64], wpa_pairwise[16], wpa_groupwise[16];
+       char auth_algs[2], wep_key0[27], wep_key1[27], wep_key2[27], wep_key3[27];
+       char mode[16];
 
-               val = nl80211_getval(ifname, res, "wpa_key_mgmt");
+       struct iwinfo_crypto_entry *c = (struct iwinfo_crypto_entry *)buf;
 
-               if (!val || strstr(val, "PSK"))
-                       c->auth_suites |= IWINFO_KMGMT_PSK;
+       /* WPA supplicant */
+       if (nl80211_wpactl_query(ifname,
+                       "pairwise_cipher", wpa_pairwise,  sizeof(wpa_pairwise),
+                       "group_cipher",    wpa_groupwise, sizeof(wpa_groupwise),
+                       "key_mgmt",        wpa_key_mgmt,  sizeof(wpa_key_mgmt),
+                       "mode",            mode,          sizeof(mode)))
+       {
+               /* WEP or Open */
+               if (!strcmp(wpa_key_mgmt, "NONE"))
+               {
+                       parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers);
+                       parse_wpa_ciphers(wpa_groupwise, &c->group_ciphers);
 
-               if (val && strstr(val, "EAP"))
-                       c->auth_suites |= IWINFO_KMGMT_8021x;
+                       if (c->pair_ciphers != 0 && c->pair_ciphers != IWINFO_CIPHER_NONE) {
+                               c->enabled     = 1;
+                               c->auth_suites = IWINFO_KMGMT_NONE;
+                               c->auth_algs   = IWINFO_AUTH_OPEN | IWINFO_AUTH_SHARED;
+                       }
+                       else {
+                               c->pair_ciphers = 0;
+                               c->group_ciphers = 0;
+                       }
+               }
 
-               if (val && strstr(val, "NONE"))
-                       c->auth_suites |= IWINFO_KMGMT_NONE;
+               /* MESH with SAE */
+               else if (!strcmp(mode, "mesh") && !strcmp(wpa_key_mgmt, "UNKNOWN"))
+               {
+                       c->enabled = 1;
+                       c->wpa_version = 4;
+                       c->auth_suites = IWINFO_KMGMT_SAE;
+                       c->pair_ciphers = IWINFO_CIPHER_CCMP;
+                       c->group_ciphers = IWINFO_CIPHER_CCMP;
+               }
 
-               if ((val = nl80211_getval(ifname, res, "wpa_pairwise")) != NULL)
+               /* WPA */
+               else
                {
-                       if (strstr(val, "TKIP"))
-                               c->pair_ciphers |= IWINFO_CIPHER_TKIP;
+                       parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers);
+                       parse_wpa_ciphers(wpa_groupwise, &c->group_ciphers);
+
+                       p = wpa_key_mgmt;
+
+                       if (!strncmp(p, "WPA2-", 5) || !strncmp(p, "WPA2/", 5))
+                       {
+                               p += 5;
+                               wpa_version = 2;
+                       }
+                       else if (!strncmp(p, "WPA-", 4))
+                       {
+                               p += 4;
+                               wpa_version = 1;
+                       }
 
-                       if (strstr(val, "CCMP"))
-                               c->pair_ciphers |= IWINFO_CIPHER_CCMP;
+                       parse_wpa_suites(p, wpa_version, &c->wpa_version, &c->auth_suites);
 
-                       if (strstr(val, "NONE"))
-                               c->pair_ciphers |= IWINFO_CIPHER_NONE;
+                       c->enabled = !!(c->wpa_version && c->auth_suites);
                }
 
-               if ((val = nl80211_getval(ifname, res, "auth_algs")) != NULL)
-               {
-                       switch(atoi(val)) {
-                               case 1:
-                                       c->auth_algs |= IWINFO_AUTH_OPEN;
-                                       break;
+               return 0;
+       }
 
-                               case 2:
-                                       c->auth_algs |= IWINFO_AUTH_SHARED;
-                                       break;
+       /* Hostapd */
+       else if (nl80211_hostapd_query(ifname,
+                               "wpa",          wpa,          sizeof(wpa),
+                               "wpa_key_mgmt", wpa_key_mgmt, sizeof(wpa_key_mgmt),
+                               "wpa_pairwise", wpa_pairwise, sizeof(wpa_pairwise),
+                               "auth_algs",    auth_algs,    sizeof(auth_algs),
+                               "wep_key0",     wep_key0,     sizeof(wep_key0),
+                               "wep_key1",     wep_key1,     sizeof(wep_key1),
+                               "wep_key2",     wep_key2,     sizeof(wep_key2),
+                               "wep_key3",     wep_key3,     sizeof(wep_key3)))
+       {
+               c->wpa_version = 0;
+
+               if (wpa_key_mgmt[0])
+               {
+                       for (p = strtok(wpa_key_mgmt, " \t"); p != NULL; p = strtok(NULL, " \t"))
+                       {
+                               if (!strncmp(p, "WPA-", 4))
+                                       p += 4;
 
-                               case 3:
-                                       c->auth_algs |= IWINFO_AUTH_OPEN;
-                                       c->auth_algs |= IWINFO_AUTH_SHARED;
-                                       break;
+                               if (!strncmp(p, "FT-", 3))
+                                       p += 3;
 
-                               default:
-                                       break;
+                               parse_wpa_suites(p, atoi(wpa), &c->wpa_version, &c->auth_suites);
                        }
 
-                       for (i = 0; i < 4; i++)
+                       c->enabled = c->wpa_version ? 1 : 0;
+               }
+
+               if (wpa_pairwise[0])
+                       parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers);
+
+               if (auth_algs[0])
+               {
+                       switch (atoi(auth_algs))
                        {
-                               snprintf(k, sizeof(k), "wep_key%d", i);
+                       case 1:
+                               c->auth_algs |= IWINFO_AUTH_OPEN;
+                               break;
 
-                               if ((val = nl80211_getval(ifname, res, k)))
-                               {
-                                       if ((strlen(val) == 5) || (strlen(val) == 10))
-                                               c->pair_ciphers |= IWINFO_CIPHER_WEP40;
+                       case 2:
+                               c->auth_algs |= IWINFO_AUTH_SHARED;
+                               break;
 
-                                       else if ((strlen(val) == 13) || (strlen(val) == 26))
-                                               c->pair_ciphers |= IWINFO_CIPHER_WEP104;
-                               }
+                       case 3:
+                               c->auth_algs |= IWINFO_AUTH_OPEN;
+                               c->auth_algs |= IWINFO_AUTH_SHARED;
+                               break;
                        }
+
+                       c->pair_ciphers |= nl80211_check_wepkey(wep_key0);
+                       c->pair_ciphers |= nl80211_check_wepkey(wep_key1);
+                       c->pair_ciphers |= nl80211_check_wepkey(wep_key2);
+                       c->pair_ciphers |= nl80211_check_wepkey(wep_key3);
+
+                       c->enabled = (c->auth_algs && c->pair_ciphers) ? 1 : 0;
                }
 
                c->group_ciphers = c->pair_ciphers;
-               c->enabled = (c->wpa_version || c->pair_ciphers) ? 1 : 0;
 
                return 0;
        }
 
+       /* Ad-Hoc or Mesh interfaces without wpa_supplicant are open */
+       else if (!nl80211_get_mode(ifname, &opmode) &&
+                (opmode == IWINFO_OPMODE_ADHOC ||
+                 opmode == IWINFO_OPMODE_MESHPOINT))
+       {
+               c->enabled = 0;
+
+               return 0;
+       }
+
+
        return -1;
 }
 
@@ -1570,24 +2039,203 @@ static int nl80211_get_phyname(const char *ifname, char *buf)
 }
 
 
-static int nl80211_get_assoclist_cb(struct nl_msg *msg, void *arg)
+static void nl80211_parse_rateinfo(struct nlattr **ri,
+                                   struct iwinfo_rate_entry *re)
+{
+       if (ri[NL80211_RATE_INFO_BITRATE32])
+               re->rate = nla_get_u32(ri[NL80211_RATE_INFO_BITRATE32]) * 100;
+       else if (ri[NL80211_RATE_INFO_BITRATE])
+               re->rate = nla_get_u16(ri[NL80211_RATE_INFO_BITRATE]) * 100;
+
+       if (ri[NL80211_RATE_INFO_HE_MCS])
+       {
+               re->is_he = 1;
+               re->mcs = nla_get_u8(ri[NL80211_RATE_INFO_HE_MCS]);
+
+               if (ri[NL80211_RATE_INFO_HE_NSS])
+                       re->nss = nla_get_u8(ri[NL80211_RATE_INFO_HE_NSS]);
+               if (ri[NL80211_RATE_INFO_HE_GI])
+                       re->he_gi = nla_get_u8(ri[NL80211_RATE_INFO_HE_GI]);
+               if (ri[NL80211_RATE_INFO_HE_DCM])
+                       re->he_dcm = nla_get_u8(ri[NL80211_RATE_INFO_HE_DCM]);
+       }
+       else if (ri[NL80211_RATE_INFO_VHT_MCS])
+       {
+               re->is_vht = 1;
+               re->mcs = nla_get_u8(ri[NL80211_RATE_INFO_VHT_MCS]);
+
+               if (ri[NL80211_RATE_INFO_VHT_NSS])
+                       re->nss = nla_get_u8(ri[NL80211_RATE_INFO_VHT_NSS]);
+       }
+       else if (ri[NL80211_RATE_INFO_MCS])
+       {
+               re->is_ht = 1;
+               re->mcs = nla_get_u8(ri[NL80211_RATE_INFO_MCS]);
+       }
+
+       if (ri[NL80211_RATE_INFO_5_MHZ_WIDTH])
+               re->mhz = 5;
+       else if (ri[NL80211_RATE_INFO_10_MHZ_WIDTH])
+               re->mhz = 10;
+       else if (ri[NL80211_RATE_INFO_40_MHZ_WIDTH])
+               re->mhz = 40;
+       else if (ri[NL80211_RATE_INFO_80_MHZ_WIDTH])
+               re->mhz = 80;
+       else if (ri[NL80211_RATE_INFO_80P80_MHZ_WIDTH] ||
+                ri[NL80211_RATE_INFO_160_MHZ_WIDTH])
+               re->mhz = 160;
+       else
+               re->mhz = 20;
+
+       if (ri[NL80211_RATE_INFO_SHORT_GI])
+               re->is_short_gi = 1;
+
+       re->is_40mhz = (re->mhz == 40);
+}
+
+static int nl80211_get_survey_cb(struct nl_msg *msg, void *arg)
+{
+       struct nl80211_array_buf *arr = arg;
+       struct iwinfo_survey_entry *e = arr->buf;
+       struct nlattr **attr = nl80211_parse(msg);
+       struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1];
+       int rc;
+
+       static const struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = {
+               [NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 },
+               [NL80211_SURVEY_INFO_NOISE]  = { .type = NLA_U8     },
+               [NL80211_SURVEY_INFO_TIME] = { .type = NLA_U64   },
+               [NL80211_SURVEY_INFO_TIME_BUSY] = { .type = NLA_U64   },
+               [NL80211_SURVEY_INFO_TIME_EXT_BUSY] = { .type = NLA_U64   },
+               [NL80211_SURVEY_INFO_TIME_RX] = { .type = NLA_U64   },
+               [NL80211_SURVEY_INFO_TIME_TX] = { .type = NLA_U64   },
+       };
+
+       rc = nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX,
+                               attr[NL80211_ATTR_SURVEY_INFO],
+                               survey_policy);
+       if (rc)
+               return NL_SKIP;
+
+       /* advance to end of array */
+       e += arr->count;
+       memset(e, 0, sizeof(*e));
+
+       if (sinfo[NL80211_SURVEY_INFO_FREQUENCY])
+               e->mhz = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);
+
+       if (sinfo[NL80211_SURVEY_INFO_NOISE])
+               e->noise = nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);
+
+       if (sinfo[NL80211_SURVEY_INFO_TIME])
+               e->active_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME]);
+
+       if (sinfo[NL80211_SURVEY_INFO_TIME_BUSY])
+               e->busy_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_BUSY]);
+
+       if (sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY])
+               e->busy_time_ext = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_EXT_BUSY]);
+
+       if (sinfo[NL80211_SURVEY_INFO_TIME_RX])
+               e->rxtime = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_RX]);
+
+       if (sinfo[NL80211_SURVEY_INFO_TIME_TX])
+               e->txtime = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_TX]);
+
+       arr->count++;
+       return NL_SKIP;
+}
+
+
+static void plink_state_to_str(char *dst, unsigned state)
+{
+       switch (state) {
+       case NL80211_PLINK_LISTEN:
+               strcpy(dst, "LISTEN");
+               break;
+       case NL80211_PLINK_OPN_SNT:
+               strcpy(dst, "OPN_SNT");
+               break;
+       case NL80211_PLINK_OPN_RCVD:
+               strcpy(dst, "OPN_RCVD");
+               break;
+       case NL80211_PLINK_CNF_RCVD:
+               strcpy(dst, "CNF_RCVD");
+               break;
+       case NL80211_PLINK_ESTAB:
+               strcpy(dst, "ESTAB");
+               break;
+       case NL80211_PLINK_HOLDING:
+               strcpy(dst, "HOLDING");
+               break;
+       case NL80211_PLINK_BLOCKED:
+               strcpy(dst, "BLOCKED");
+               break;
+       default:
+               strcpy(dst, "UNKNOWN");
+               break;
+       }
+}
+
+static void power_mode_to_str(char *dst, struct nlattr *a)
+{
+       enum nl80211_mesh_power_mode pm = nla_get_u32(a);
+
+       switch (pm) {
+       case NL80211_MESH_POWER_ACTIVE:
+               strcpy(dst, "ACTIVE");
+               break;
+       case NL80211_MESH_POWER_LIGHT_SLEEP:
+               strcpy(dst, "LIGHT SLEEP");
+               break;
+       case NL80211_MESH_POWER_DEEP_SLEEP:
+               strcpy(dst, "DEEP SLEEP");
+               break;
+       default:
+               strcpy(dst, "UNKNOWN");
+               break;
+       }
+}
+
+static int nl80211_get_assoclist_cb(struct nl_msg *msg, void *arg)
 {
        struct nl80211_array_buf *arr = arg;
        struct iwinfo_assoclist_entry *e = arr->buf;
        struct nlattr **attr = nl80211_parse(msg);
        struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
        struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
+       struct nl80211_sta_flag_update *sta_flags;
 
-       static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
+       static const struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
                [NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32    },
                [NL80211_STA_INFO_RX_PACKETS]    = { .type = NLA_U32    },
                [NL80211_STA_INFO_TX_PACKETS]    = { .type = NLA_U32    },
                [NL80211_STA_INFO_RX_BITRATE]    = { .type = NLA_NESTED },
                [NL80211_STA_INFO_TX_BITRATE]    = { .type = NLA_NESTED },
                [NL80211_STA_INFO_SIGNAL]        = { .type = NLA_U8     },
+               [NL80211_STA_INFO_SIGNAL_AVG]    = { .type = NLA_U8     },
+               [NL80211_STA_INFO_RX_BYTES]      = { .type = NLA_U32    },
+               [NL80211_STA_INFO_TX_BYTES]      = { .type = NLA_U32    },
+               [NL80211_STA_INFO_RX_BYTES64]    = { .type = NLA_U64    },
+               [NL80211_STA_INFO_TX_BYTES64]    = { .type = NLA_U64    },
+               [NL80211_STA_INFO_TX_RETRIES]    = { .type = NLA_U32    },
+               [NL80211_STA_INFO_TX_FAILED]     = { .type = NLA_U32    },
+               [NL80211_STA_INFO_CONNECTED_TIME]= { .type = NLA_U32    },
+               [NL80211_STA_INFO_RX_DROP_MISC]  = { .type = NLA_U64    },
+               [NL80211_STA_INFO_T_OFFSET]      = { .type = NLA_U64    },
+               [NL80211_STA_INFO_STA_FLAGS] =
+                       { .minlen = sizeof(struct nl80211_sta_flag_update) },
+               [NL80211_STA_INFO_EXPECTED_THROUGHPUT]   = { .type = NLA_U32    },
+               /* mesh */
+               [NL80211_STA_INFO_LLID]          = { .type = NLA_U16    },
+               [NL80211_STA_INFO_PLID]          = { .type = NLA_U16    },
+               [NL80211_STA_INFO_PLINK_STATE]   = { .type = NLA_U8     },
+               [NL80211_STA_INFO_LOCAL_PM]      = { .type = NLA_U32    },
+               [NL80211_STA_INFO_PEER_PM]       = { .type = NLA_U32    },
+               [NL80211_STA_INFO_NONPEER_PM]    = { .type = NLA_U32    },
        };
 
-       static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
+       static const 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   },
@@ -1608,9 +2256,15 @@ static int nl80211_get_assoclist_cb(struct nl_msg *msg, void *arg)
                if (sinfo[NL80211_STA_INFO_SIGNAL])
                        e->signal = nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]);
 
+               if (sinfo[NL80211_STA_INFO_SIGNAL_AVG])
+                       e->signal_avg = nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL_AVG]);
+
                if (sinfo[NL80211_STA_INFO_INACTIVE_TIME])
                        e->inactive = nla_get_u32(sinfo[NL80211_STA_INFO_INACTIVE_TIME]);
 
+               if (sinfo[NL80211_STA_INFO_CONNECTED_TIME])
+                       e->connected_time = nla_get_u32(sinfo[NL80211_STA_INFO_CONNECTED_TIME]);
+
                if (sinfo[NL80211_STA_INFO_RX_PACKETS])
                        e->rx_packets = nla_get_u32(sinfo[NL80211_STA_INFO_RX_PACKETS]);
 
@@ -1620,37 +2274,85 @@ static int nl80211_get_assoclist_cb(struct nl_msg *msg, void *arg)
                if (sinfo[NL80211_STA_INFO_RX_BITRATE] &&
                    !nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX,
                                      sinfo[NL80211_STA_INFO_RX_BITRATE], rate_policy))
-               {
-                       if (rinfo[NL80211_RATE_INFO_BITRATE])
-                               e->rx_rate.rate =
-                                       nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100;
-
-                       if (rinfo[NL80211_RATE_INFO_MCS])
-                               e->rx_rate.mcs = nla_get_u8(rinfo[NL80211_RATE_INFO_MCS]);
-
-                       if (rinfo[NL80211_RATE_INFO_40_MHZ_WIDTH])
-                               e->rx_rate.is_40mhz = 1;
-
-                       if (rinfo[NL80211_RATE_INFO_SHORT_GI])
-                               e->rx_rate.is_short_gi = 1;
-               }
+                       nl80211_parse_rateinfo(rinfo, &e->rx_rate);
 
                if (sinfo[NL80211_STA_INFO_TX_BITRATE] &&
                    !nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX,
                                      sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy))
+                       nl80211_parse_rateinfo(rinfo, &e->tx_rate);
+
+               if (sinfo[NL80211_STA_INFO_RX_BYTES64])
+                       e->rx_bytes = nla_get_u64(sinfo[NL80211_STA_INFO_RX_BYTES64]);
+               else if (sinfo[NL80211_STA_INFO_RX_BYTES])
+                       e->rx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_RX_BYTES]);
+
+               if (sinfo[NL80211_STA_INFO_TX_BYTES64])
+                       e->tx_bytes = nla_get_u64(sinfo[NL80211_STA_INFO_TX_BYTES64]);
+               else if (sinfo[NL80211_STA_INFO_TX_BYTES])
+                       e->tx_bytes = nla_get_u32(sinfo[NL80211_STA_INFO_TX_BYTES]);
+
+               if (sinfo[NL80211_STA_INFO_TX_RETRIES])
+                       e->tx_retries = nla_get_u32(sinfo[NL80211_STA_INFO_TX_RETRIES]);
+
+               if (sinfo[NL80211_STA_INFO_TX_FAILED])
+                       e->tx_failed = nla_get_u32(sinfo[NL80211_STA_INFO_TX_FAILED]);
+
+               if (sinfo[NL80211_STA_INFO_T_OFFSET])
+                       e->t_offset = nla_get_u64(sinfo[NL80211_STA_INFO_T_OFFSET]);
+
+               if (sinfo[NL80211_STA_INFO_RX_DROP_MISC])
+                       e->rx_drop_misc = nla_get_u64(sinfo[NL80211_STA_INFO_RX_DROP_MISC]);
+
+               if (sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT])
+                       e->thr = nla_get_u32(sinfo[NL80211_STA_INFO_EXPECTED_THROUGHPUT]);
+
+               /* mesh */
+               if (sinfo[NL80211_STA_INFO_LLID])
+                       e->llid = nla_get_u16(sinfo[NL80211_STA_INFO_LLID]);
+
+               if (sinfo[NL80211_STA_INFO_PLID])
+                       e->plid = nla_get_u16(sinfo[NL80211_STA_INFO_PLID]);
+
+               if (sinfo[NL80211_STA_INFO_PLINK_STATE])
+                       plink_state_to_str(e->plink_state,
+                               nla_get_u8(sinfo[NL80211_STA_INFO_PLINK_STATE]));
+
+               if (sinfo[NL80211_STA_INFO_LOCAL_PM])
+                       power_mode_to_str(e->local_ps, sinfo[NL80211_STA_INFO_LOCAL_PM]);
+               if (sinfo[NL80211_STA_INFO_PEER_PM])
+                       power_mode_to_str(e->peer_ps, sinfo[NL80211_STA_INFO_PEER_PM]);
+               if (sinfo[NL80211_STA_INFO_NONPEER_PM])
+                       power_mode_to_str(e->nonpeer_ps, sinfo[NL80211_STA_INFO_NONPEER_PM]);
+
+               /* Station flags */
+               if (sinfo[NL80211_STA_INFO_STA_FLAGS])
                {
-                       if (rinfo[NL80211_RATE_INFO_BITRATE])
-                               e->tx_rate.rate =
-                                       nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100;
+                       sta_flags = (struct nl80211_sta_flag_update *)
+                               nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]);
+
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_AUTHORIZED) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_AUTHORIZED))
+                               e->is_authorized = 1;
+
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_AUTHENTICATED) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
+                               e->is_authenticated = 1;
+
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE))
+                               e->is_preamble_short = 1;
 
-                       if (rinfo[NL80211_RATE_INFO_MCS])
-                               e->tx_rate.mcs = nla_get_u8(rinfo[NL80211_RATE_INFO_MCS]);
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_WME) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_WME))
+                               e->is_wme = 1;
 
-                       if (rinfo[NL80211_RATE_INFO_40_MHZ_WIDTH])
-                               e->tx_rate.is_40mhz = 1;
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_MFP) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_MFP))
+                               e->is_mfp = 1;
 
-                       if (rinfo[NL80211_RATE_INFO_SHORT_GI])
-                               e->tx_rate.is_short_gi = 1;
+                       if (sta_flags->mask & BIT(NL80211_STA_FLAG_TDLS_PEER) &&
+                           sta_flags->set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+                               e->is_tdls = 1;
                }
        }
 
@@ -1660,12 +2362,26 @@ static int nl80211_get_assoclist_cb(struct nl_msg *msg, void *arg)
        return NL_SKIP;
 }
 
+static int nl80211_get_survey(const char *ifname, char *buf, int *len)
+{
+       struct nl80211_array_buf arr = { .buf = buf, .count = 0 };
+       int rc;
+
+       rc = nl80211_request(ifname, NL80211_CMD_GET_SURVEY,
+                       NLM_F_DUMP, nl80211_get_survey_cb, &arr);
+       if (!rc)
+               *len = (arr.count * sizeof(struct iwinfo_survey_entry));
+       else
+               *len = 0;
+
+       return 0;
+}
+
 static int nl80211_get_assoclist(const char *ifname, char *buf, int *len)
 {
        DIR *d;
        int i, noise = 0;
        struct dirent *de;
-       struct nl80211_msg_conveyor *req;
        struct nl80211_array_buf arr = { .buf = buf, .count = 0 };
        struct iwinfo_assoclist_entry *e;
 
@@ -1677,14 +2393,8 @@ static int nl80211_get_assoclist(const char *ifname, char *buf, int *len)
                            (!de->d_name[strlen(ifname)] ||
                             !strncmp(&de->d_name[strlen(ifname)], ".sta", 4)))
                        {
-                               req = nl80211_msg(de->d_name, NL80211_CMD_GET_STATION,
-                                                 NLM_F_DUMP);
-
-                               if (req)
-                               {
-                                       nl80211_send(req, nl80211_get_assoclist_cb, &arr);
-                                       nl80211_free(req);
-                               }
+                               nl80211_request(de->d_name, NL80211_CMD_GET_STATION,
+                                               NLM_F_DUMP, nl80211_get_assoclist_cb, &arr);
                        }
                }
 
@@ -1711,7 +2421,7 @@ static int nl80211_get_txpwrlist_cb(struct nl_msg *msg, void *arg)
        struct nlattr *freqs[NL80211_FREQUENCY_ATTR_MAX + 1];
        struct nlattr *band, *freq;
 
-       static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
+       static const 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 },
@@ -1752,25 +2462,20 @@ static int nl80211_get_txpwrlist_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_txpwrlist(const char *ifname, char *buf, int *len)
 {
-       int ch_cur;
+       int err, ch_cur;
        int dbm_max = -1, dbm_cur, dbm_cnt;
-       struct nl80211_msg_conveyor *req;
        struct iwinfo_txpwrlist_entry entry;
 
        if (nl80211_get_channel(ifname, &ch_cur))
                ch_cur = 0;
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
-       if (req)
-       {
-               /* initialize the value pointer with channel for callback */
-               dbm_max = ch_cur;
+       /* initialize the value pointer with channel for callback */
+       dbm_max = ch_cur;
 
-               nl80211_send(req, nl80211_get_txpwrlist_cb, &dbm_max);
-               nl80211_free(req);
-       }
+       err = nl80211_request(ifname, NL80211_CMD_GET_WIPHY, 0,
+                             nl80211_get_txpwrlist_cb, &dbm_max);
 
-       if (dbm_max > 0)
+       if (!err)
        {
                for (dbm_cur = 0, dbm_cnt = 0;
                     dbm_cur < dbm_max;
@@ -1795,53 +2500,39 @@ static int nl80211_get_txpwrlist(const char *ifname, char *buf, int *len)
        return -1;
 }
 
-static void nl80211_get_scancrypto(const char *spec,
-       struct iwinfo_crypto_entry *c)
+static void nl80211_get_scancrypto(char *spec, struct iwinfo_crypto_entry *c)
 {
-       if (strstr(spec, "WPA") || strstr(spec, "WEP"))
-       {
-               c->enabled = 1;
-
-               if (strstr(spec, "WPA2-") && strstr(spec, "WPA-"))
-                       c->wpa_version = 3;
-
-               else if (strstr(spec, "WPA2"))
-                       c->wpa_version = 2;
+       int wpa_version = 0;
+       char *p, *q, *proto, *suites;
 
-               else if (strstr(spec, "WPA"))
-                       c->wpa_version = 1;
-
-               else if (strstr(spec, "WEP"))
-                       c->auth_algs = IWINFO_AUTH_OPEN | IWINFO_AUTH_SHARED;
-
-
-               if (strstr(spec, "PSK"))
-                       c->auth_suites |= IWINFO_KMGMT_PSK;
-
-               if (strstr(spec, "802.1X") || strstr(spec, "EAP"))
-                       c->auth_suites |= IWINFO_KMGMT_8021x;
-
-               if (strstr(spec, "WPA-NONE"))
-                       c->auth_suites |= IWINFO_KMGMT_NONE;
+       c->enabled = 0;
 
+       for (p = strtok_r(spec, "[]", &q); p; p = strtok_r(NULL, "[]", &q)) {
+               if (!strcmp(p, "WEP")) {
+                       c->enabled      = 1;
+                       c->auth_suites  = IWINFO_KMGMT_NONE;
+                       c->auth_algs    = IWINFO_AUTH_OPEN | IWINFO_AUTH_SHARED;
+                       c->pair_ciphers = IWINFO_CIPHER_WEP40 | IWINFO_CIPHER_WEP104;
+                       break;
+               }
 
-               if (strstr(spec, "TKIP"))
-                       c->pair_ciphers |= IWINFO_CIPHER_TKIP;
+               proto = strtok(p, "-");
+               suites = strtok(NULL, "]");
 
-               if (strstr(spec, "CCMP"))
-                       c->pair_ciphers |= IWINFO_CIPHER_CCMP;
+               if (!proto || !suites)
+                       continue;
 
-               if (strstr(spec, "WEP-40"))
-                       c->pair_ciphers |= IWINFO_CIPHER_WEP40;
+               if (!strcmp(proto, "WPA2") || !strcmp(proto, "RSN"))
+                       wpa_version = 2;
+               else if (!strcmp(proto, "WPA"))
+                       wpa_version = 1;
+               else
+                       continue;
 
-               if (strstr(spec, "WEP-104"))
-                       c->pair_ciphers |= IWINFO_CIPHER_WEP104;
+               c->enabled = 1;
 
-               c->group_ciphers = c->pair_ciphers;
-       }
-       else
-       {
-               c->enabled = 0;
+               parse_wpa_suites(suites, wpa_version, &c->wpa_version, &c->auth_suites);
+               parse_wpa_ciphers(suites, &c->pair_ciphers);
        }
 }
 
@@ -1865,9 +2556,12 @@ static void nl80211_get_scanlist_ie(struct nlattr **bss,
                switch (ie[0])
                {
                case 0: /* SSID */
-                       len = min(ie[1], IWINFO_ESSID_MAX_SIZE);
-                       memcpy(e->ssid, ie + 2, len);
-                       e->ssid[len] = 0;
+               case 114: /* Mesh ID */
+                       if (e->ssid[0] == 0) {
+                               len = min(ie[1], IWINFO_ESSID_MAX_SIZE);
+                               memcpy(e->ssid, ie + 2, len);
+                               e->ssid[len] = 0;
+                       }
                        break;
 
                case 48: /* RSN */
@@ -1880,6 +2574,20 @@ static void nl80211_get_scanlist_ie(struct nlattr **bss,
                                iwinfo_parse_rsn(&e->crypto, ie + 6, ie[1] - 4,
                                                 IWINFO_CIPHER_TKIP, IWINFO_KMGMT_PSK);
                        break;
+               case 61: /* HT operation */
+                       if (ie[1] >= 3) {
+                               e->ht_chan_info.primary_chan = ie[2];
+                               e->ht_chan_info.secondary_chan_off = ie[3] & 0x3;
+                               e->ht_chan_info.chan_width = (ie[4] & 0x4)>>2;
+                       }
+                       break;
+               case 192: /* VHT operation */
+                       if (ie[1] >= 3) {
+                               e->vht_chan_info.chan_width = ie[2];
+                               e->vht_chan_info.center_chan_1 = ie[3];
+                               e->vht_chan_info.center_chan_2 = ie[4];
+                       }
+                       break;
                }
 
                ielen -= ie[1] + 2;
@@ -1896,18 +2604,18 @@ static int nl80211_get_scanlist_cb(struct nl_msg *msg, void *arg)
        struct nlattr **tb = nl80211_parse(msg);
        struct nlattr *bss[NL80211_BSS_MAX + 1];
 
-       static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+       static const 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_BSSID]                = { 0 },
                [NL80211_BSS_BEACON_INTERVAL]      = { .type = NLA_U16 },
                [NL80211_BSS_CAPABILITY]           = { .type = NLA_U16 },
-               [NL80211_BSS_INFORMATION_ELEMENTS] = {                 },
+               [NL80211_BSS_INFORMATION_ELEMENTS] = { 0 },
                [NL80211_BSS_SIGNAL_MBM]           = { .type = NLA_U32 },
                [NL80211_BSS_SIGNAL_UNSPEC]        = { .type = NLA_U8  },
                [NL80211_BSS_STATUS]               = { .type = NLA_U32 },
                [NL80211_BSS_SEEN_MS_AGO]          = { .type = NLA_U32 },
-               [NL80211_BSS_BEACON_IES]           = {                 },
+               [NL80211_BSS_BEACON_IES]           = { 0 },
        };
 
        if (!tb[NL80211_ATTR_BSS] ||
@@ -1937,8 +2645,11 @@ static int nl80211_get_scanlist_cb(struct nl_msg *msg, void *arg)
                sl->e->crypto.enabled = 1;
 
        if (bss[NL80211_BSS_FREQUENCY])
-               sl->e->channel = nl80211_freq2channel(nla_get_u32(
-                       bss[NL80211_BSS_FREQUENCY]));
+       {
+               sl->e->mhz = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
+               sl->e->band = nl80211_freq2band(sl->e->mhz);
+               sl->e->channel = nl80211_freq2channel(sl->e->mhz);
+       }
 
        if (bss[NL80211_BSS_INFORMATION_ELEMENTS])
                nl80211_get_scanlist_ie(bss, sl->e);
@@ -1973,27 +2684,25 @@ static int nl80211_get_scanlist_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_scanlist_nl(const char *ifname, char *buf, int *len)
 {
-       struct nl80211_msg_conveyor *req;
        struct nl80211_scanlist sl = { .e = (struct iwinfo_scanlist_entry *)buf };
 
-       req = nl80211_msg(ifname, NL80211_CMD_TRIGGER_SCAN, 0);
-       if (req)
-       {
-               nl80211_send(req, NULL, NULL);
-               nl80211_free(req);
-       }
+       if (nl80211_request(ifname, NL80211_CMD_TRIGGER_SCAN, 0, NULL, NULL))
+               goto out;
 
-       nl80211_wait("nl80211", "scan", NL80211_CMD_NEW_SCAN_RESULTS);
+       if (nl80211_wait("nl80211", "scan",
+                        NL80211_CMD_NEW_SCAN_RESULTS, NL80211_CMD_SCAN_ABORTED))
+               goto out;
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_scanlist_cb, &sl);
-               nl80211_free(req);
-       }
+       if (nl80211_request(ifname, NL80211_CMD_GET_SCAN, NLM_F_DUMP,
+                           nl80211_get_scanlist_cb, &sl))
+               goto out;
 
        *len = sl.len * sizeof(struct iwinfo_scanlist_entry);
-       return *len ? 0 : -1;
+       return 0;
+
+out:
+       *len = 0;
+       return -1;
 }
 
 static int wpasupp_ssid_decode(const char *in, char *out, int outlen)
@@ -2028,7 +2737,7 @@ static int wpasupp_ssid_decode(const char *in, char *out, int outlen)
                                break;
 
                        case 'e':
-                               out[len++] = '\e'; in++;
+                               out[len++] = '\033'; in++;
                                break;
 
                        case 'x':
@@ -2055,124 +2764,188 @@ static int wpasupp_ssid_decode(const char *in, char *out, int outlen)
        return len;
 }
 
-static int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
+static int nl80211_get_scanlist_wpactl(const char *ifname, char *buf, int *len)
 {
-       int freq, rssi, qmax, count, mode;
-       char *res;
-       char ssid[129] = { 0 };
-       char bssid[18] = { 0 };
-       char cipher[256] = { 0 };
+       int sock, qmax, rssi, tries, count = -1, ready = 0;
+       char *pos, *line, *bssid, *freq, *signal, *flags, *ssid, reply[4096];
+       struct sockaddr_un local = { 0 };
+       struct iwinfo_scanlist_entry *e = (struct iwinfo_scanlist_entry *)buf;
 
-       /* Got a radioX pseudo interface, find some interface on it or create one */
-       if (!strncmp(ifname, "radio", 5))
+       sock = nl80211_wpactl_connect(ifname, &local);
+
+       if (sock < 0)
+               return sock;
+
+       send(sock, "ATTACH", 6, 0);
+       send(sock, "SCAN", 4, 0);
+
+       /*
+        * wait for scan results:
+        *   nl80211_wpactl_recv() will use a timeout of 256ms and we need to scan
+        *   72 channels at most. We'll also receive two "OK" messages acknowledging
+        *   the "ATTACH" and "SCAN" commands and the driver might need a bit extra
+        *   time to process the results, so try 72 + 2 + 1 times.
+        */
+       for (tries = 0; tries < 75; tries++)
        {
-               /* Reuse existing interface */
-               if ((res = nl80211_phy2ifname(ifname)) != NULL)
+               if (nl80211_wpactl_recv(sock, reply, sizeof(reply)) <= 0)
+                       continue;
+
+               /* got an event notification */
+               if (reply[0] == '<')
                {
-                       return nl80211_get_scanlist(res, buf, len);
+                       /* scan results are ready */
+                       if (strstr(reply, "CTRL-EVENT-SCAN-RESULTS"))
+                       {
+                               /* send "SCAN_RESULTS" command */
+                               ready = (send(sock, "SCAN_RESULTS", 12, 0) == 12);
+                               break;
+                       }
+
+                       /* is another unrelated event, retry */
+                       tries--;
                }
 
-               /* Need to spawn a temporary iface for scanning */
-               else if ((res = nl80211_ifadd(ifname)) != NULL)
+               /* scanning already in progress, keep awaiting results */
+               else if (!strcmp(reply, "FAIL-BUSY\n"))
                {
-                       count = nl80211_get_scanlist(res, buf, len);
-                       nl80211_ifdel(res);
-                       return count;
+                       tries--;
                }
-       }
 
-       struct iwinfo_scanlist_entry *e = (struct iwinfo_scanlist_entry *)buf;
+               /* another failure, abort */
+               else if (!strncmp(reply, "FAIL-", 5))
+               {
+                       break;
+               }
+       }
 
-       /* WPA supplicant */
-       if ((res = nl80211_wpactl_info(ifname, "SCAN", "CTRL-EVENT-SCAN-RESULTS")))
+       /* receive and parse scan results if the wait above didn't time out */
+       while (ready && nl80211_wpactl_recv(sock, reply, sizeof(reply)) > 0)
        {
-               if ((res = nl80211_wpactl_info(ifname, "SCAN_RESULTS", NULL)))
+               /* received an event notification, receive again */
+               if (reply[0] == '<')
+                       continue;
+
+               nl80211_get_quality_max(ifname, &qmax);
+
+               for (line = strtok_r(reply, "\n", &pos);
+                    line != NULL;
+                    line = strtok_r(NULL, "\n", &pos))
                {
-                       nl80211_get_quality_max(ifname, &qmax);
+                       /* skip header line */
+                       if (count < 0)
+                       {
+                               count++;
+                               continue;
+                       }
 
-                       count = -1;
+                       bssid  = strtok(line, "\t");
+                       freq   = strtok(NULL, "\t");
+                       signal = strtok(NULL, "\t");
+                       flags  = strtok(NULL, "\t");
+                       ssid   = strtok(NULL, "\n");
 
-                       do {
-                               if (res[0] == '<')
-                               {
-                                       /* skip log lines */
-                                       goto nextline;
-                               }
-                               if (count < 0)
-                               {
-                                       /* skip header line */
-                                       count++;
-                                       goto nextline;
-                               }
-                               if (sscanf(res, "%17s %d %d %255s%*[ \t]%128[^\n]\n",
-                                             bssid, &freq, &rssi, cipher, ssid) < 5)
-                               {
-                                       /* skip malformed lines */
-                                       goto nextline;
-                               }
-                               /* BSSID */
-                               e->mac[0] = strtol(&bssid[0],  NULL, 16);
-                               e->mac[1] = strtol(&bssid[3],  NULL, 16);
-                               e->mac[2] = strtol(&bssid[6],  NULL, 16);
-                               e->mac[3] = strtol(&bssid[9],  NULL, 16);
-                               e->mac[4] = strtol(&bssid[12], NULL, 16);
-                               e->mac[5] = strtol(&bssid[15], NULL, 16);
-
-                               /* SSID */
+                       if (!bssid || !freq || !signal || !flags)
+                               continue;
+
+                       /* BSSID */
+                       e->mac[0] = strtol(&bssid[0],  NULL, 16);
+                       e->mac[1] = strtol(&bssid[3],  NULL, 16);
+                       e->mac[2] = strtol(&bssid[6],  NULL, 16);
+                       e->mac[3] = strtol(&bssid[9],  NULL, 16);
+                       e->mac[4] = strtol(&bssid[12], NULL, 16);
+                       e->mac[5] = strtol(&bssid[15], NULL, 16);
+
+                       /* SSID */
+                       if (ssid)
                                wpasupp_ssid_decode(ssid, e->ssid, sizeof(e->ssid));
+                       else
+                               e->ssid[0] = 0;
+
+                       /* Mode */
+                       if (strstr(flags, "[MESH]"))
+                               e->mode = IWINFO_OPMODE_MESHPOINT;
+                       else if (strstr(flags, "[IBSS]"))
+                               e->mode = IWINFO_OPMODE_ADHOC;
+                       else
+                               e->mode = IWINFO_OPMODE_MASTER;
+
+                       /* Channel */
+                       e->mhz = atoi(freq);
+                       e->band = nl80211_freq2band(e->mhz);
+                       e->channel = nl80211_freq2channel(e->mhz);
 
-                               /* Mode (assume master) */
-                               if (strstr(cipher,"[MESH]"))
-                                       e->mode = IWINFO_OPMODE_MESHPOINT;
-                               else
-                                       e->mode = IWINFO_OPMODE_MASTER;
+                       /* Signal */
+                       rssi = atoi(signal);
+                       e->signal = rssi;
 
-                               /* Channel */
-                               e->channel = nl80211_freq2channel(freq);
+                       /* Quality */
+                       if (rssi < 0)
+                       {
+                               /* The cfg80211 wext compat layer assumes a signal range
+                                * of -110 dBm to -40 dBm, the quality value is derived
+                                * by adding 110 to the signal level */
+                               if (rssi < -110)
+                                       rssi = -110;
+                               else if (rssi > -40)
+                                       rssi = -40;
+
+                               e->quality = (rssi + 110);
+                       }
+                       else
+                       {
+                               e->quality = rssi;
+                       }
 
-                               /* Signal */
-                               e->signal = rssi;
+                       /* Max. Quality */
+                       e->quality_max = qmax;
 
-                               /* Quality */
-                               if (rssi < 0)
-                               {
-                                       /* The cfg80211 wext compat layer assumes a signal range
-                                        * of -110 dBm to -40 dBm, the quality value is derived
-                                        * by adding 110 to the signal level */
-                                       if (rssi < -110)
-                                               rssi = -110;
-                                       else if (rssi > -40)
-                                               rssi = -40;
-
-                                       e->quality = (rssi + 110);
-                               }
-                               else
-                               {
-                                       e->quality = rssi;
-                               }
+                       /* Crypto */
+                       nl80211_get_scancrypto(flags, &e->crypto);
 
-                               /* Max. Quality */
-                               e->quality_max = qmax;
+                       count++;
+                       e++;
+               }
 
-                               /* Crypto */
-                               nl80211_get_scancrypto(cipher, &e->crypto);
+               *len = count * sizeof(struct iwinfo_scanlist_entry);
+               break;
+       }
 
-                               count++;
-                               e++;
+       close(sock);
+       unlink(local.sun_path);
+
+       return (count >= 0) ? 0 : -1;
+}
 
-                               memset(ssid, 0, sizeof(ssid));
-                               memset(bssid, 0, sizeof(bssid));
-                               memset(cipher, 0, sizeof(cipher));
+static int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
+{
+       char *res;
+       int rv, mode;
 
-                       nextline:
-                               /* advance to next line */
-                               while( *res && *res++ != '\n' );
-                       }
-                       while( *res );
+       *len = 0;
 
-                       *len = count * sizeof(struct iwinfo_scanlist_entry);
-                       return 0;
+       /* Got a radioX pseudo interface, find some interface on it or create one */
+       if (!strncmp(ifname, "radio", 5))
+       {
+               /* Reuse existing interface */
+               if ((res = nl80211_phy2ifname(ifname)) != NULL)
+               {
+                       return nl80211_get_scanlist(res, buf, len);
                }
+
+               /* Need to spawn a temporary iface for scanning */
+               else if ((res = nl80211_ifadd(ifname)) != NULL)
+               {
+                       rv = nl80211_get_scanlist(res, buf, len);
+                       nl80211_ifdel(res);
+                       return rv;
+               }
+       }
+
+       /* WPA supplicant */
+       if (!nl80211_get_scanlist_wpactl(ifname, buf, len))
+       {
+               return 0;
        }
 
        /* station / ad-hoc / monitor scan */
@@ -2195,16 +2968,16 @@ static int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
                        if (!iwinfo_ifup(ifname))
                                return -1;
 
-                       nl80211_get_scanlist_nl(ifname, buf, len);
+                       rv = nl80211_get_scanlist_nl(ifname, buf, len);
                        iwinfo_ifdown(ifname);
-                       return 0;
+                       return rv;
                }
 
                /* Spawn a new scan interface */
                else
                {
                        if (!(res = nl80211_ifadd(ifname)))
-                               goto out;
+                               return -1;
 
                        iwinfo_ifmac(res);
 
@@ -2212,7 +2985,7 @@ static int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
                         * additional interface and there's no need to tear down the ap */
                        if (iwinfo_ifup(res))
                        {
-                               nl80211_get_scanlist_nl(res, buf, len);
+                               rv = nl80211_get_scanlist_nl(res, buf, len);
                                iwinfo_ifdown(res);
                        }
 
@@ -2220,15 +2993,16 @@ static int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
                         * during scan */
                        else if (iwinfo_ifdown(ifname) && iwinfo_ifup(res))
                        {
-                               nl80211_get_scanlist_nl(res, buf, len);
+                               rv = nl80211_get_scanlist_nl(res, buf, len);
                                iwinfo_ifdown(res);
                                iwinfo_ifup(ifname);
                                nl80211_hostapd_hup(ifname);
                        }
+                       else
+                               rv = -1;
 
-               out:
                        nl80211_ifdel(res);
-                       return 0;
+                       return rv;
                }
        }
 
@@ -2240,47 +3014,63 @@ static int nl80211_get_freqlist_cb(struct nl_msg *msg, void *arg)
        int bands_remain, freqs_remain;
 
        struct nl80211_array_buf *arr = arg;
-       struct iwinfo_freqlist_entry *e = arr->buf;
+       struct iwinfo_freqlist_entry *e;
 
        struct nlattr **attr = nl80211_parse(msg);
        struct nlattr *bands[NL80211_BAND_ATTR_MAX + 1];
        struct nlattr *freqs[NL80211_FREQUENCY_ATTR_MAX + 1];
        struct nlattr *band, *freq;
 
-       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  },
-       };
-
-       nla_for_each_nested(band, attr[NL80211_ATTR_WIPHY_BANDS], bands_remain)
-       {
-               nla_parse(bands, NL80211_BAND_ATTR_MAX,
-                         nla_data(band), nla_len(band), NULL);
+       e = arr->buf;
+       e += arr->count;
 
-               nla_for_each_nested(freq, bands[NL80211_BAND_ATTR_FREQS], freqs_remain)
+       if (attr[NL80211_ATTR_WIPHY_BANDS]) {
+               nla_for_each_nested(band, attr[NL80211_ATTR_WIPHY_BANDS], bands_remain)
                {
-                       nla_parse(freqs, NL80211_FREQUENCY_ATTR_MAX,
-                                 nla_data(freq), nla_len(freq), NULL);
-
-                       if (!freqs[NL80211_FREQUENCY_ATTR_FREQ] ||
-                           freqs[NL80211_FREQUENCY_ATTR_DISABLED])
-                               continue;
-
-                       e->mhz = nla_get_u32(freqs[NL80211_FREQUENCY_ATTR_FREQ]);
-                       e->channel = nl80211_freq2channel(e->mhz);
-
-                       e->restricted = (
-                               freqs[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] ||
-                               freqs[NL80211_FREQUENCY_ATTR_NO_IBSS]      ||
-                               freqs[NL80211_FREQUENCY_ATTR_RADAR]
-                       ) ? 1 : 0;
+                       nla_parse(bands, NL80211_BAND_ATTR_MAX,
+                                 nla_data(band), nla_len(band), NULL);
 
-                       e++;
-                       arr->count++;
+                       if (bands[NL80211_BAND_ATTR_FREQS]) {
+                               nla_for_each_nested(freq, bands[NL80211_BAND_ATTR_FREQS], freqs_remain)
+                               {
+                                       nla_parse(freqs, NL80211_FREQUENCY_ATTR_MAX,
+                                                 nla_data(freq), nla_len(freq), NULL);
+
+                                       if (!freqs[NL80211_FREQUENCY_ATTR_FREQ] ||
+                                           freqs[NL80211_FREQUENCY_ATTR_DISABLED])
+                                               continue;
+
+                                       e->band = nl80211_get_band(band->nla_type);
+                                       e->mhz = nla_get_u32(freqs[NL80211_FREQUENCY_ATTR_FREQ]);
+                                       e->channel = nl80211_freq2channel(e->mhz);
+
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_HT40_MINUS])
+                                               e->flags |= IWINFO_FREQ_NO_HT40MINUS;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_HT40_PLUS])
+                                               e->flags |= IWINFO_FREQ_NO_HT40PLUS;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_80MHZ])
+                                               e->flags |= IWINFO_FREQ_NO_80MHZ;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_160MHZ])
+                                               e->flags |= IWINFO_FREQ_NO_160MHZ;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_20MHZ])
+                                               e->flags |= IWINFO_FREQ_NO_20MHZ;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_10MHZ])
+                                               e->flags |= IWINFO_FREQ_NO_10MHZ;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_HE])
+                                               e->flags |= IWINFO_FREQ_NO_HE;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_NO_IR] &&
+                                           !freqs[NL80211_FREQUENCY_ATTR_RADAR])
+                                               e->flags |= IWINFO_FREQ_NO_IR;
+                                       if (freqs[NL80211_FREQUENCY_ATTR_INDOOR_ONLY])
+                                               e->flags |= IWINFO_FREQ_INDOOR_ONLY;
+
+                                       /* keep backwards compatibility */
+                                       e->restricted = (e->flags & IWINFO_FREQ_NO_IR) ? 1 : 0;
+
+                                       e++;
+                                       arr->count++;
+                               }
+                       }
                }
        }
 
@@ -2289,22 +3079,27 @@ static int nl80211_get_freqlist_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_freqlist(const char *ifname, char *buf, int *len)
 {
-       struct nl80211_msg_conveyor *req;
+       struct nl80211_msg_conveyor *cv;
        struct nl80211_array_buf arr = { .buf = buf, .count = 0 };
+       uint32_t features = nl80211_get_protocol_features(ifname);
+       int flags;
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_freqlist_cb, &arr);
-               nl80211_free(req);
-       }
+       flags = features & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP ? NLM_F_DUMP : 0;
+       cv = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, flags);
+       if (!cv)
+               goto out;
 
-       if (arr.count > 0)
-       {
-               *len = arr.count * sizeof(struct iwinfo_freqlist_entry);
-               return 0;
-       }
+       NLA_PUT_FLAG(cv->msg, NL80211_ATTR_SPLIT_WIPHY_DUMP);
+       if (nl80211_send(cv, nl80211_get_freqlist_cb, &arr))
+               goto out;
 
+       *len = arr.count * sizeof(struct iwinfo_freqlist_entry);
+       return 0;
+
+nla_put_failure:
+       nl80211_free(cv);
+out:
+       *len = 0;
        return -1;
 }
 
@@ -2323,25 +3118,16 @@ static int nl80211_get_country_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_country(const char *ifname, char *buf)
 {
-       int rv = -1;
-       struct nl80211_msg_conveyor *req;
-
-       req = nl80211_msg(ifname, NL80211_CMD_GET_REG, 0);
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_country_cb, buf);
-               nl80211_free(req);
-
-               if (buf[0])
-                       rv = 0;
-       }
+       if (nl80211_request(ifname, NL80211_CMD_GET_REG, 0,
+                           nl80211_get_country_cb, buf))
+               return -1;
 
-       return rv;
+       return 0;
 }
 
 static int nl80211_get_countrylist(const char *ifname, char *buf, int *len)
 {
-       int i, count;
+       int count;
        struct iwinfo_country_entry *e = (struct iwinfo_country_entry *)buf;
        const struct iwinfo_iso3166_label *l;
 
@@ -2350,66 +3136,136 @@ static int nl80211_get_countrylist(const char *ifname, char *buf, int *len)
                e->iso3166 = l->iso3166;
                e->ccode[0] = (l->iso3166 / 256);
                e->ccode[1] = (l->iso3166 % 256);
+               e->ccode[2] = 0;
        }
 
        *len = (count * sizeof(struct iwinfo_country_entry));
        return 0;
 }
 
-static int nl80211_get_hwmodelist_cb(struct nl_msg *msg, void *arg)
+
+struct nl80211_modes
 {
-       int *modes = arg;
-       int bands_remain, freqs_remain;
-       uint16_t caps = 0;
-       uint32_t vht_caps = 0;
+       bool ok;
+       uint32_t hw;
+       uint32_t ht;
+
+       uint8_t bands;
+
+       uint16_t nl_ht;
+       uint32_t nl_vht;
+       uint16_t he_phy_cap[6];
+};
+
+static void nl80211_eval_modelist(struct nl80211_modes *m)
+{
+       /* Treat any nonzero capability as 11n */
+       if (m->nl_ht > 0)
+       {
+               m->hw |= IWINFO_80211_N;
+               m->ht |= IWINFO_HTMODE_HT20;
+
+               if (m->nl_ht & (1 << 1))
+                       m->ht |= IWINFO_HTMODE_HT40;
+       }
+
+       if (m->he_phy_cap[0] != 0) {
+               m->hw |= IWINFO_80211_AX;
+               m->ht |= IWINFO_HTMODE_HE20;
+
+               if (m->he_phy_cap[0] & BIT(9))
+                       m->ht |= IWINFO_HTMODE_HE40;
+               if (m->he_phy_cap[0] & BIT(10))
+                       m->ht |= IWINFO_HTMODE_HE40 | IWINFO_HTMODE_HE80;
+               if (m->he_phy_cap[0] & BIT(11))
+                       m->ht |= IWINFO_HTMODE_HE160;
+               if (m->he_phy_cap[0] & BIT(12))
+                       m->ht |= IWINFO_HTMODE_HE160 | IWINFO_HTMODE_HE80_80;
+       }
+
+       if (m->bands & IWINFO_BAND_24)
+       {
+               m->hw |= IWINFO_80211_B;
+               m->hw |= IWINFO_80211_G;
+       }
+
+       if (m->bands & IWINFO_BAND_5)
+       {
+               /* Treat any nonzero capability as 11ac */
+               if (m->nl_vht > 0)
+               {
+                       m->hw |= IWINFO_80211_AC;
+                       m->ht |= IWINFO_HTMODE_VHT20 | IWINFO_HTMODE_VHT40 | IWINFO_HTMODE_VHT80;
+
+                       switch ((m->nl_vht >> 2) & 3)
+                       {
+                       case 2:
+                               m->ht |= IWINFO_HTMODE_VHT80_80;
+                               /* fall through */
+
+                       case 1:
+                               m->ht |= IWINFO_HTMODE_VHT160;
+                       }
+               }
+               else
+               {
+                       m->hw |= IWINFO_80211_A;
+               }
+       }
+
+       if (m->bands & IWINFO_BAND_60)
+       {
+               m->hw |= IWINFO_80211_AD;
+       }
+
+}
+
+static int nl80211_get_modelist_cb(struct nl_msg *msg, void *arg)
+{
+       struct nl80211_modes *m = arg;
+       int bands_remain;
        struct nlattr **attr = nl80211_parse(msg);
        struct nlattr *bands[NL80211_BAND_ATTR_MAX + 1];
-       struct nlattr *freqs[NL80211_FREQUENCY_ATTR_MAX + 1];
-       struct nlattr *band, *freq;
-
-       *modes = 0;
+       struct nlattr *band;
 
        if (attr[NL80211_ATTR_WIPHY_BANDS])
        {
                nla_for_each_nested(band, attr[NL80211_ATTR_WIPHY_BANDS], bands_remain)
                {
+                       m->bands |= nl80211_get_band(band->nla_type);
+
                        nla_parse(bands, NL80211_BAND_ATTR_MAX,
                                  nla_data(band), nla_len(band), NULL);
 
                        if (bands[NL80211_BAND_ATTR_HT_CAPA])
-                               caps = nla_get_u16(bands[NL80211_BAND_ATTR_HT_CAPA]);
-
-                       /* Treat any nonzero capability as 11n */
-                       if (caps > 0)
-                               *modes |= IWINFO_80211_N;
+                               m->nl_ht = nla_get_u16(bands[NL80211_BAND_ATTR_HT_CAPA]);
 
                        if (bands[NL80211_BAND_ATTR_VHT_CAPA])
-                               vht_caps = nla_get_u32(bands[NL80211_BAND_ATTR_VHT_CAPA]);
-
-                       /* Treat any nonzero capability as 11ac */
-                       if (vht_caps > 0)
-                               *modes |= IWINFO_80211_AC;
-
-                       nla_for_each_nested(freq, bands[NL80211_BAND_ATTR_FREQS],
-                                           freqs_remain)
-                       {
-                               nla_parse(freqs, NL80211_FREQUENCY_ATTR_MAX,
-                                         nla_data(freq), nla_len(freq), NULL);
-
-                               if (!freqs[NL80211_FREQUENCY_ATTR_FREQ])
-                                       continue;
-
-                               if (nla_get_u32(freqs[NL80211_FREQUENCY_ATTR_FREQ]) < 2485)
-                               {
-                                       *modes |= IWINFO_80211_B;
-                                       *modes |= IWINFO_80211_G;
-                               }
-                               else if (!(*modes & IWINFO_80211_AC))
-                               {
-                                       *modes |= IWINFO_80211_A;
+                               m->nl_vht = nla_get_u32(bands[NL80211_BAND_ATTR_VHT_CAPA]);
+
+                       if (bands[NL80211_BAND_ATTR_IFTYPE_DATA]) {
+                               struct nlattr *tb[NL80211_BAND_IFTYPE_ATTR_MAX + 1];
+                               struct nlattr *nl_iftype;
+                               int rem_band;
+                               int len;
+
+                               nla_for_each_nested(nl_iftype, bands[NL80211_BAND_ATTR_IFTYPE_DATA], rem_band) {
+                                       nla_parse(tb, NL80211_BAND_IFTYPE_ATTR_MAX,
+                                                 nla_data(nl_iftype), nla_len(nl_iftype), NULL);
+                                       if (tb[NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY]) {
+                                               len = nla_len(tb[NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY]);
+
+                                               if (len > sizeof(m->he_phy_cap) - 1)
+                                                       len = sizeof(m->he_phy_cap) - 1;
+                                               memcpy(&((__u8 *)m->he_phy_cap)[1],
+                                                       nla_data(tb[NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY]),
+                                                       len);
+                                       }
                                }
                        }
                }
+
+               m->ok = 1;
        }
 
        return NL_SKIP;
@@ -2417,18 +3273,149 @@ static int nl80211_get_hwmodelist_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_hwmodelist(const char *ifname, int *buf)
 {
-       struct nl80211_msg_conveyor *req;
+       struct nl80211_msg_conveyor *cv;
+       struct nl80211_modes m = {};
+       uint32_t features = nl80211_get_protocol_features(ifname);
+       int flags;
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
-       if (req)
-       {
-               nl80211_send(req, nl80211_get_hwmodelist_cb, buf);
-               nl80211_free(req);
+       flags = features & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP ? NLM_F_DUMP : 0;
+       cv = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, flags);
+       if (!cv)
+               goto out;
+
+       NLA_PUT_FLAG(cv->msg, NL80211_ATTR_SPLIT_WIPHY_DUMP);
+       if (nl80211_send(cv, nl80211_get_modelist_cb, &m))
+               goto nla_put_failure;
+
+       nl80211_eval_modelist(&m);
+
+       *buf = m.hw;
+
+       return 0;
+
+nla_put_failure:
+       nl80211_free(cv);
+out:
+       return -1;
+}
+
+struct chan_info {
+       int width;
+       int mode;
+};
+
+static int nl80211_get_htmode_cb(struct nl_msg *msg, void *arg)
+{
+       struct nlattr **tb = nl80211_parse(msg);
+       struct nlattr *cur;
+       struct chan_info *chn = arg;
+
+       if ((cur = tb[NL80211_ATTR_CHANNEL_WIDTH]))
+               chn->width = nla_get_u32(cur);
+
+       if ((cur = tb[NL80211_ATTR_BSS_HT_OPMODE]))
+               chn->mode = nla_get_u32(cur);
+
+       return NL_SKIP;
+}
+
+static int nl80211_get_htmode(const char *ifname, int *buf)
+{
+       struct chan_info chn = { 0 };
+       char *res, b[2] = { 0 };
+       int err;
+       bool he = false;
+
+       res = nl80211_phy2ifname(ifname);
+       *buf = 0;
+
+       err =  nl80211_request(res ? res : ifname,
+                               NL80211_CMD_GET_INTERFACE, 0,
+                               nl80211_get_htmode_cb, &chn);
+       if (err)
+               return -1;
+
+       if (nl80211_hostapd_query(res ? res : ifname, "ieee80211ax", b, sizeof(b)))
+               he = b[0] == '1';
+       else if (nl80211_wpactl_query(res ? res : ifname, "wifi_generation", b, sizeof(b)))
+               he = b[0] == '6';
+
+       switch (chn.width) {
+       case NL80211_CHAN_WIDTH_20:
+               if (he)
+                       *buf = IWINFO_HTMODE_HE20;
+               else if (chn.mode == -1)
+                       *buf = IWINFO_HTMODE_VHT20;
+               else
+                       *buf = IWINFO_HTMODE_HT20;
+               break;
+       case NL80211_CHAN_WIDTH_40:
+               if (he)
+                       *buf = IWINFO_HTMODE_HE40;
+               else if (chn.mode == -1)
+                       *buf = IWINFO_HTMODE_VHT40;
+               else
+                       *buf = IWINFO_HTMODE_HT40;
+               break;
+       case NL80211_CHAN_WIDTH_80:
+               if (he)
+                       *buf = IWINFO_HTMODE_HE80;
+               else
+                       *buf = IWINFO_HTMODE_VHT80;
+               break;
+       case NL80211_CHAN_WIDTH_80P80:
+               if (he)
+                       *buf = IWINFO_HTMODE_HE80_80;
+               else
+                       *buf = IWINFO_HTMODE_VHT80_80;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               if (he)
+                       *buf = IWINFO_HTMODE_HE160;
+               else
+                       *buf = IWINFO_HTMODE_VHT160;
+               break;
+       case NL80211_CHAN_WIDTH_5:
+       case NL80211_CHAN_WIDTH_10:
+       case NL80211_CHAN_WIDTH_20_NOHT:
+               *buf = IWINFO_HTMODE_NOHT;
+               break;
+       default:
+               return -1;
        }
 
-       return *buf ? 0 : -1;
+       return 0;
 }
 
+static int nl80211_get_htmodelist(const char *ifname, int *buf)
+{
+       struct nl80211_msg_conveyor *cv;
+       struct nl80211_modes m = {};
+       uint32_t features = nl80211_get_protocol_features(ifname);
+       int flags;
+
+       flags = features & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP ? NLM_F_DUMP : 0;
+       cv = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, flags);
+       if (!cv)
+               goto out;
+
+       NLA_PUT_FLAG(cv->msg, NL80211_ATTR_SPLIT_WIPHY_DUMP);
+       if (nl80211_send(cv, nl80211_get_modelist_cb, &m))
+               goto nla_put_failure;
+
+       nl80211_eval_modelist(&m);
+
+       *buf = m.ht;
+
+       return 0;
+
+nla_put_failure:
+       nl80211_free(cv);
+out:
+       return -1;
+}
+
+
 static int nl80211_get_ifcomb_cb(struct nl_msg *msg, void *arg)
 {
        struct nlattr **attr = nl80211_parse(msg);
@@ -2442,12 +3429,12 @@ static int nl80211_get_ifcomb_cb(struct nl_msg *msg, void *arg)
 
        nla_for_each_nested(comb, attr[NL80211_ATTR_INTERFACE_COMBINATIONS], comb_rem)
        {
-               static struct nla_policy iface_combination_policy[NUM_NL80211_IFACE_COMB] = {
+               static const struct nla_policy iface_combination_policy[NUM_NL80211_IFACE_COMB] = {
                        [NL80211_IFACE_COMB_LIMITS] = { .type = NLA_NESTED },
                        [NL80211_IFACE_COMB_MAXNUM] = { .type = NLA_U32 },
                };
                struct nlattr *tb_comb[NUM_NL80211_IFACE_COMB+1];
-               static struct nla_policy iface_limit_policy[NUM_NL80211_IFACE_LIMIT] = {
+               static const struct nla_policy iface_limit_policy[NUM_NL80211_IFACE_LIMIT] = {
                        [NL80211_IFACE_LIMIT_TYPES] = { .type = NLA_NESTED },
                        [NL80211_IFACE_LIMIT_MAX] = { .type = NLA_U32 },
                };
@@ -2484,50 +3471,69 @@ static int nl80211_get_ifcomb_cb(struct nl_msg *msg, void *arg)
 
 static int nl80211_get_mbssid_support(const char *ifname, int *buf)
 {
-       struct nl80211_msg_conveyor *req;
+       if (nl80211_request(ifname, NL80211_CMD_GET_WIPHY, 0,
+                           nl80211_get_ifcomb_cb, buf))
+               return -1;
 
-       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
-       if (!req)
+       return 0;
+}
+
+static int nl80211_hardware_id_from_fdt(struct iwinfo_hardware_id *id, const char *ifname)
+{
+       char *phy, path[PATH_MAX];
+
+       /* Try to determine the phy name from the given interface */
+       phy = nl80211_ifname2phy(ifname);
+
+       snprintf(path, sizeof(path), "/sys/class/%s/%s/device/of_node/compatible",
+                phy ? "ieee80211" : "net", phy ? phy : ifname);
+
+       if (nl80211_readstr(path, id->compatible, sizeof(id->compatible)) <= 0)
                return -1;
 
-       nl80211_send(req, nl80211_get_ifcomb_cb, buf);
-       nl80211_free(req);
        return 0;
 }
 
+
 static int nl80211_get_hardware_id(const char *ifname, char *buf)
 {
-       int rv;
-       char *res;
+       struct iwinfo_hardware_id *id = (struct iwinfo_hardware_id *)buf;
+       char *phy, num[8], path[PATH_MAX];
+       int i;
 
-       /* Got a radioX pseudo interface, find some interface on it or create one */
-       if (!strncmp(ifname, "radio", 5))
-       {
-               /* Reuse existing interface */
-               if ((res = nl80211_phy2ifname(ifname)) != NULL)
-               {
-                       rv = wext_ops.hardware_id(res, buf);
-               }
+       struct { const char *path; uint16_t *dest; } lookup[] = {
+               { "vendor", &id->vendor_id },
+               { "device", &id->device_id },
+               { "subsystem_vendor", &id->subsystem_vendor_id },
+               { "subsystem_device", &id->subsystem_device_id },
+               { "../idVendor", &id->subsystem_vendor_id },
+               { "../idProduct", &id->subsystem_device_id }
+       };
 
-               /* Need to spawn a temporary iface for finding IDs */
-               else if ((res = nl80211_ifadd(ifname)) != NULL)
-               {
-                       rv = wext_ops.hardware_id(res, buf);
-                       nl80211_ifdel(res);
-               }
-       }
-       else
-       {
-               rv = wext_ops.hardware_id(ifname, buf);
-       }
+       memset(id, 0, sizeof(*id));
+
+       /* Try to determine the phy name from the given interface */
+       phy = nl80211_ifname2phy(ifname);
 
-       /* Failed to obtain hardware IDs, search board config */
-       if (rv)
+       for (i = 0; i < ARRAY_SIZE(lookup); i++)
        {
-               rv = iwinfo_hardware_id_from_mtd((struct iwinfo_hardware_id *)buf);
+               snprintf(path, sizeof(path), "/sys/class/%s/%s/device/%s",
+                        phy ? "ieee80211" : "net",
+                        phy ? phy : ifname, lookup[i].path);
+
+               if (nl80211_readstr(path, num, sizeof(num)) > 0)
+                       *lookup[i].dest = strtoul(num, NULL, 16);
        }
 
-       return rv;
+       /* Failed to obtain hardware PCI/USB IDs... */
+       if (id->vendor_id == 0 && id->device_id == 0 &&
+           id->subsystem_vendor_id == 0 && id->subsystem_device_id == 0)
+               /* ... first fallback to FDT ... */
+               if (nl80211_hardware_id_from_fdt(id, ifname) == -1)
+                       /* ... then board config */
+                       return iwinfo_hardware_id_from_mtd(id);
+
+       return 0;
 }
 
 static const struct iwinfo_hardware_entry *
@@ -2577,12 +3583,36 @@ static int nl80211_get_frequency_offset(const char *ifname, int *buf)
 
 static int nl80211_lookup_phyname(const char *section, char *buf)
 {
+       const char *name;
        int idx;
 
-       if ((idx = nl80211_phy_idx_from_uci(section)) < 0)
+       if (!strncmp(section, "path=", 5))
+               idx = nl80211_phy_idx_from_path(section + 5);
+       else if (!strncmp(section, "macaddr=", 8))
+               idx = nl80211_phy_idx_from_macaddr(section + 8);
+       else
+               idx = nl80211_phy_idx_from_uci(section);
+
+       if (idx < 0)
+               return -1;
+
+       name = nl80211_phyidx2name(idx);
+       if (!name)
+               return -1;
+
+       strcpy(buf, name);
+       return 0;
+}
+
+static int nl80211_phy_path(const char *phyname, const char **path)
+{
+       if (strchr(phyname, '/'))
+               return -1;
+
+       *path = nl80211_phy_path_str(phyname);
+       if (!*path)
                return -1;
 
-       sprintf(buf, "phy%d", idx);
        return 0;
 }
 
@@ -2590,6 +3620,8 @@ const struct iwinfo_ops nl80211_ops = {
        .name             = "nl80211",
        .probe            = nl80211_probe,
        .channel          = nl80211_get_channel,
+       .center_chan1     = nl80211_get_center_chan1,
+       .center_chan2     = nl80211_get_center_chan2,
        .frequency        = nl80211_get_frequency,
        .frequency_offset = nl80211_get_frequency_offset,
        .txpower          = nl80211_get_txpower,
@@ -2601,6 +3633,8 @@ const struct iwinfo_ops nl80211_ops = {
        .quality_max      = nl80211_get_quality_max,
        .mbssid_support   = nl80211_get_mbssid_support,
        .hwmodelist       = nl80211_get_hwmodelist,
+       .htmodelist       = nl80211_get_htmodelist,
+       .htmode           = nl80211_get_htmode,
        .mode             = nl80211_get_mode,
        .ssid             = nl80211_get_ssid,
        .bssid            = nl80211_get_bssid,
@@ -2614,6 +3648,8 @@ const struct iwinfo_ops nl80211_ops = {
        .scanlist         = nl80211_get_scanlist,
        .freqlist         = nl80211_get_freqlist,
        .countrylist      = nl80211_get_countrylist,
+       .survey           = nl80211_get_survey,
        .lookup_phy       = nl80211_lookup_phyname,
+       .phy_path         = nl80211_phy_path,
        .close            = nl80211_close
 };