libiwinfo: correctly map BSS vars when parsing hostapd config
[project/luci.git] / contrib / package / iwinfo / src / iwinfo_nl80211.c
index 7f27e25338063e9bb31bfc71232f58f3cd2c965e..21797c13de9b76ee8bc4c28377d20bea88e4f929 100644 (file)
@@ -77,17 +77,7 @@ static int nl80211_init(void)
 
 
 err:
-       if( nls && nls->nl_sock )
-               nl_socket_free(nls->nl_sock);
-
-       if( nls && nls->nl_cache )
-               nl_cache_free(nls->nl_cache);
-
-       if( nls )
-               free(nls);
-
-       nls = NULL;
-
+       nl80211_close();
        return err;
 }
 
@@ -131,14 +121,14 @@ static int nl80211_msg_response(struct nl_msg *msg, void *arg)
 
 static void nl80211_free(struct nl80211_msg_conveyor *cv)
 {
-       if( cv && cv->cb )
-               nl_cb_put(cv->cb);
-
-       if( cv && cv->msg )
-               nlmsg_free(cv->msg);
-
        if( cv )
        {
+               if( cv->cb )
+                       nl_cb_put(cv->cb);
+
+               if( cv->msg )
+                       nlmsg_free(cv->msg);
+
                cv->cb  = NULL;
                cv->msg = NULL;
        }
@@ -148,19 +138,21 @@ static struct nl80211_msg_conveyor * nl80211_msg(const char *ifname, int cmd, in
 {
        static struct nl80211_msg_conveyor cv;
 
-       int ifidx;
+       int ifidx = -1, phyidx = -1;
        struct nl_msg *req = NULL;
        struct nl_cb *cb = NULL;
 
        if( nl80211_init() < 0 )
                goto err;
 
-       if( !strncmp(ifname, "mon.", 4) )
+       if( !strncmp(ifname, "radio", 5) )
+               phyidx = atoi(&ifname[5]);
+       else if( !strncmp(ifname, "mon.", 4) )
                ifidx = if_nametoindex(&ifname[4]);
        else
                ifidx = if_nametoindex(ifname);
 
-       if( ifidx < 0 )
+       if( (ifidx < 0) && (phyidx < 0) )
                return NULL;
 
        req = nlmsg_alloc();
@@ -174,7 +166,11 @@ static struct nl80211_msg_conveyor * nl80211_msg(const char *ifname, int cmd, in
        genlmsg_put(req, 0, 0, genl_family_get_id(nls->nl80211), 0,
                flags, cmd, 0);
 
-       NLA_PUT_U32(req, NL80211_ATTR_IFINDEX, ifidx);
+       if( ifidx > -1 )
+               NLA_PUT_U32(req, NL80211_ATTR_IFINDEX, ifidx);
+
+       if( phyidx > -1 )
+               NLA_PUT_U32(req, NL80211_ATTR_WIPHY, phyidx);
 
        nlmsg_get(req);
 
@@ -240,13 +236,16 @@ static int nl80211_freq2channel(int freq)
     return (freq / 5) - 1000;
 }
 
-static char * nl80211_getval(const char *buf, const char *key)
+static char * nl80211_getval(const char *ifname, const char *buf, const char *key)
 {
        int i, len;
        char lkey[64] = { 0 };
        const char *ln = buf;
        static char lval[256] = { 0 };
 
+       int matched_if = ifname ? 0 : 1;
+
+
        for( i = 0, len = strlen(buf); i < len; i++ )
        {
                if( !lkey[0] && (buf[i] == ' ' || buf[i] == '\t') )
@@ -260,12 +259,20 @@ static char * nl80211_getval(const char *buf, const char *key)
                }
                else if( buf[i] == '\n' )
                {
-                       if( lkey[0] && !strcmp(lkey, key) )
+                       if( lkey[0] )
                        {
                                memcpy(lval, ln + strlen(lkey) + 1,
                                        min(sizeof(lval) - 1, &buf[i] - ln - strlen(lkey) - 1));
 
-                               return lval;
+                               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];
@@ -389,12 +396,114 @@ out:
        return rv;
 }
 
+static char * nl80211_phy2ifname(const char *ifname)
+{
+       int fd, phyidx = 0;
+       char buffer[64];
+       static char nif[IFNAMSIZ] = { 0 };
+
+       DIR *d;
+       struct dirent *e;
+
+       if( !strncmp(ifname, "radio", 5) )
+       {
+               phyidx = atoi(&ifname[5]);
+
+               if( (d = opendir("/sys/class/net")) != NULL )
+               {
+                       while( (e = readdir(d)) != NULL )
+                       {
+                               snprintf(buffer, sizeof(buffer),
+                                       "/sys/class/net/%s/phy80211/index", e->d_name);
+
+                               if( (fd = open(buffer, O_RDONLY)) > 0 )
+                               {
+                                       if( (read(fd, buffer, sizeof(buffer)) > 0) &&
+                                           (atoi(buffer) == phyidx) )
+                                       {
+                                               strncpy(nif, e->d_name, sizeof(nif));
+                                       }
+
+                                       close(fd);
+                               }
+
+                               if( nif[0] )
+                                       break;
+                       }
+
+                       closedir(d);
+               }
+       }
+
+       return nif[0] ? nif : NULL;
+}
+
+static char * nl80211_add_tempif(const char *ifname)
+{
+       int phyidx;
+       char *rv = NULL;
+       static char nif[IFNAMSIZ] = { 0 };
+       struct nl80211_msg_conveyor *req, *res;
+
+       req = nl80211_msg(ifname, NL80211_CMD_NEW_INTERFACE, 0);
+       if( req )
+       {
+               snprintf(nif, sizeof(nif), "tmp.%s", ifname);
+
+               NLA_PUT_STRING(req->msg, NL80211_ATTR_IFNAME, nif);
+               NLA_PUT_U32(req->msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION);
+
+               res = nl80211_send(req);
+               if( res )
+               {
+                       rv = nif;
+                       nl80211_free(res);
+               }
+
+       nla_put_failure:
+               nl80211_free(req);
+       }
+
+       return rv;
+}
+
+static void nl80211_del_tempif(const char *ifname)
+{
+       struct nl80211_msg_conveyor *req, *res;
+
+       req = nl80211_msg(ifname, NL80211_CMD_DEL_INTERFACE, 0);
+       if( req )
+       {
+               NLA_PUT_STRING(req->msg, NL80211_ATTR_IFNAME, ifname);
+
+               nl80211_free(nl80211_send(req));
+
+       nla_put_failure:
+               nl80211_free(req);
+       }
+}
+
 
 int nl80211_probe(const char *ifname)
 {
        return !!nl80211_ifname2phy(ifname);
 }
 
+void nl80211_close(void)
+{
+       if( nls )
+       {
+               if( nls->nl_sock )
+                       nl_socket_free(nls->nl_sock);
+
+               if( nls->nl_cache )
+                       nl_cache_free(nls->nl_cache);
+
+               free(nls);
+               nls = NULL;
+       }
+}
+
 int nl80211_get_mode(const char *ifname, char *buf)
 {
        return wext_get_mode(ifname, buf);
@@ -409,7 +518,7 @@ int nl80211_get_ssid(const char *ifname, char *buf)
                return 0;
        }
        else if( (ssid = nl80211_hostapd_info(ifname)) &&
-                (ssid = nl80211_getval(ssid, "ssid")) )
+                (ssid = nl80211_getval(ifname, ssid, "ssid")) )
        {
                memcpy(buf, ssid, strlen(ssid));
                return 0;
@@ -428,7 +537,7 @@ int nl80211_get_bssid(const char *ifname, char *buf)
                return 0;
        }
        else if( (bssid = nl80211_hostapd_info(ifname)) &&
-                (bssid = nl80211_getval(bssid, "bssid")) )
+                (bssid = nl80211_getval(ifname, bssid, "bssid")) )
        {
                mac[0] = strtol(&bssid[0],  NULL, 16);
                mac[1] = strtol(&bssid[3],  NULL, 16);
@@ -670,10 +779,9 @@ int nl80211_get_encryption(const char *ifname, char *buf)
        struct iwinfo_crypto_entry *c = (struct iwinfo_crypto_entry *)buf;
 
        /* Hostapd */
-       if( (res = nl80211_hostapd_info(ifname)) &&
-           nl80211_getval(res, "interface") )
+       if( (res = nl80211_hostapd_info(ifname)) )
        {
-               if( (val = nl80211_getval(res, "auth_algs")) && (val > 0) )
+               if( (val = nl80211_getval(ifname, res, "auth_algs")) && (val > 0) )
                {
                        c->auth_suites |= IWINFO_KMGMT_NONE;
 
@@ -699,7 +807,7 @@ int nl80211_get_encryption(const char *ifname, char *buf)
                        {
                                snprintf(k, sizeof(k), "wep_key%d", i);
 
-                               if( (val = nl80211_getval(res, k)) )
+                               if( (val = nl80211_getval(ifname, res, k)) )
                                {
                                        if( (strlen(val) == 5) || (strlen(val) == 10) )
                                                c->pair_ciphers |= IWINFO_CIPHER_WEP40;
@@ -715,11 +823,11 @@ int nl80211_get_encryption(const char *ifname, char *buf)
                }
 
 
-               if( (val = nl80211_getval(res, "wpa")) != NULL )
+               if( (val = nl80211_getval(ifname, res, "wpa")) != NULL )
                        c->wpa_version = atoi(val);
 
 
-               val = nl80211_getval(res, "wpa_key_mgmt");
+               val = nl80211_getval(ifname, res, "wpa_key_mgmt");
 
                if( !val || strstr(val, "PSK") )
                        c->auth_suites |= IWINFO_KMGMT_PSK;
@@ -731,7 +839,7 @@ int nl80211_get_encryption(const char *ifname, char *buf)
                        c->auth_suites |= IWINFO_KMGMT_NONE;
 
 
-               if( (val = nl80211_getval(res, "wpa_pairwise")) != NULL )
+               if( (val = nl80211_getval(ifname, res, "wpa_pairwise")) != NULL )
                {
                        if( strstr(val, "TKIP") )
                                c->pair_ciphers |= IWINFO_CIPHER_TKIP;
@@ -752,7 +860,7 @@ int nl80211_get_encryption(const char *ifname, char *buf)
 
        /* WPA supplicant */
        else if( (res = nl80211_wpasupp_info(ifname, "STATUS")) &&
-                (val = nl80211_getval(res, "pairwise_cipher")) )
+                (val = nl80211_getval(NULL, res, "pairwise_cipher")) )
        {
                /* WEP */
                if( strstr(val, "WEP") )
@@ -789,7 +897,7 @@ int nl80211_get_encryption(const char *ifname, char *buf)
                                c->pair_ciphers |= IWINFO_CIPHER_WEP104;
 
 
-                       if( (val = nl80211_getval(res, "group_cipher")) )
+                       if( (val = nl80211_getval(NULL, res, "group_cipher")) )
                        {
                                if( strstr(val, "TKIP") )
                                        c->group_ciphers |= IWINFO_CIPHER_TKIP;
@@ -808,7 +916,7 @@ int nl80211_get_encryption(const char *ifname, char *buf)
                        }
 
 
-                       if( (val = nl80211_getval(res, "key_mgmt")) )
+                       if( (val = nl80211_getval(NULL, res, "key_mgmt")) )
                        {
                                if( strstr(val, "WPA2") )
                                        c->wpa_version = 2;
@@ -924,7 +1032,7 @@ int nl80211_get_txpwrlist(const char *ifname, char *buf, int *len)
        };
 
        if( nl80211_get_channel(ifname, &ch_cur) )
-               return -1;
+               ch_cur = 0;
 
        req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
        if( req )
@@ -947,7 +1055,7 @@ int nl80211_get_txpwrlist(const char *ifname, char *buf, int *len)
                                        ch_cmp = nl80211_freq2channel(
                                                nla_get_u32(freqs[NL80211_FREQUENCY_ATTR_FREQ]));
 
-                                       if( (ch_cmp == ch_cur) &&
+                                       if( (!ch_cur || (ch_cmp == ch_cur)) &&
                                            freqs[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] )
                                        {
                                                dbm_max = (int)(0.01 * nla_get_u32(
@@ -1047,6 +1155,24 @@ int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
        char bssid[18] = { 0 };
        char cipher[256] = { 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_add_tempif(ifname)) != NULL )
+               {
+                       count = nl80211_get_scanlist(res, buf, len);
+                       nl80211_del_tempif(res);
+                       return count;
+               }
+       }
+
        struct iwinfo_scanlist_entry *e = (struct iwinfo_scanlist_entry *)buf;
 
        /* WPA supplicant */
@@ -1129,40 +1255,55 @@ int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
        {
                if( (res = nl80211_ifname2phy(ifname)) != NULL )
                {
-                       /*
-                        * This is a big ugly hack, just look away.
-                        */
+                       /* Got a temp interface, don't create yet another one */
+                       if( !strncmp(ifname, "tmp.", 4) )
+                       {
+                               sprintf(cmd, "ifconfig %s up 2>/dev/null", ifname);
+                               if( WEXITSTATUS(system(cmd)) )
+                                       return -1;
 
-                       sprintf(cmd, "ifconfig %s down 2>/dev/null", ifname);
-                       if( WEXITSTATUS(system(cmd)) )
-                               goto out;
+                               wext_get_scanlist(ifname, buf, len);
 
-                       sprintf(cmd, "iw phy %s interface add scan.%s "
-                               "type station 2>/dev/null", res, ifname);
-                       if( WEXITSTATUS(system(cmd)) )
-                               goto out;
+                               sprintf(cmd, "ifconfig %s down 2>/dev/null", ifname);
+                               (void) WEXITSTATUS(system(cmd));
 
-                       sprintf(cmd, "ifconfig scan.%s up 2>/dev/null", ifname);
-                       if( WEXITSTATUS(system(cmd)) )
-                               goto out;
+                               return 0;
+                       }
 
-                       sprintf(cmd, "scan.%s", ifname);
-                       wext_get_scanlist(cmd, buf, len);
+                       /* Spawn a new scan interface */
+                       else
+                       {
+                               sprintf(cmd, "ifconfig %s down 2>/dev/null", ifname);
+                               if( WEXITSTATUS(system(cmd)) )
+                                       goto out;
 
-       out:
-                       sprintf(cmd, "ifconfig scan.%s down 2>/dev/null", ifname);
-                       (void) WEXITSTATUS(system(cmd));
+                               sprintf(cmd, "iw phy %s interface add scan.%s "
+                                       "type station 2>/dev/null", res, ifname);
+                               if( WEXITSTATUS(system(cmd)) )
+                                       goto out;
 
-                       sprintf(cmd, "iw dev scan.%s del 2>/dev/null", ifname);
-                       (void) WEXITSTATUS(system(cmd));
+                               sprintf(cmd, "ifconfig scan.%s up 2>/dev/null", ifname);
+                               if( WEXITSTATUS(system(cmd)) )
+                                       goto out;
 
-                       sprintf(cmd, "ifconfig %s up 2>/dev/null", ifname);
-                       (void) WEXITSTATUS(system(cmd));
+                               sprintf(cmd, "scan.%s", ifname);
+                               wext_get_scanlist(cmd, buf, len);
 
-                       sprintf(cmd, "killall -HUP hostapd 2>/dev/null");
-                       (void) WEXITSTATUS(system(cmd));
+                       out:
+                               sprintf(cmd, "ifconfig scan.%s down 2>/dev/null", ifname);
+                               (void) WEXITSTATUS(system(cmd));
 
-                       return 0;
+                               sprintf(cmd, "iw dev scan.%s del 2>/dev/null", ifname);
+                               (void) WEXITSTATUS(system(cmd));
+
+                               sprintf(cmd, "ifconfig %s up 2>/dev/null", ifname);
+                               (void) WEXITSTATUS(system(cmd));
+
+                               sprintf(cmd, "killall -HUP hostapd 2>/dev/null");
+                               (void) WEXITSTATUS(system(cmd));
+
+                               return 0;
+                       }
                }
        }
 
@@ -1171,7 +1312,60 @@ int nl80211_get_scanlist(const char *ifname, char *buf, int *len)
 
 int nl80211_get_freqlist(const char *ifname, char *buf, int *len)
 {
-       return wext_get_freqlist(ifname, buf, len);
+       int count = 0, bands_remain, freqs_remain;
+       struct nl80211_msg_conveyor *req, *res;
+       struct nlattr *bands[NL80211_BAND_ATTR_MAX + 1];
+       struct nlattr *freqs[NL80211_FREQUENCY_ATTR_MAX + 1];
+       struct nlattr *band, *freq;
+       struct iwinfo_freqlist_entry *e = (struct iwinfo_freqlist_entry *)buf;
+
+       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
+       if( req )
+       {
+               res = nl80211_send(req);
+               if( res )
+               {
+                       nla_for_each_nested(band,
+                               res->attr[NL80211_ATTR_WIPHY_BANDS], bands_remain)
+                       {
+                               nla_parse(bands, NL80211_BAND_ATTR_MAX, nla_data(band),
+                                         nla_len(band), NULL);
+
+                               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->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;
+
+                                       e++;
+                                       count++;
+                               }
+                       }
+                       nl80211_free(res);
+               }
+               nl80211_free(req);
+       }
+
+       if( count > 0 )
+       {
+               *len = count * sizeof(struct iwinfo_freqlist_entry);
+               return 0;
+       }
+
+       return -1;
 }
 
 int nl80211_get_country(const char *ifname, char *buf)
@@ -1215,6 +1409,62 @@ int nl80211_get_countrylist(const char *ifname, char *buf, int *len)
        return 0;
 }
 
+int nl80211_get_hwmodelist(const char *ifname, int *buf)
+{
+       int bands_remain, freqs_remain;
+       struct nl80211_msg_conveyor *req, *res;
+       struct nlattr *bands[NL80211_BAND_ATTR_MAX + 1];
+       struct nlattr *freqs[NL80211_FREQUENCY_ATTR_MAX + 1];
+       struct nlattr *band, *freq;
+       uint16_t caps = 0;
+
+       req = nl80211_msg(ifname, NL80211_CMD_GET_WIPHY, 0);
+       if( req )
+       {
+               res = nl80211_send(req);
+               if( res )
+               {
+                       nla_for_each_nested(band,
+                               res->attr[NL80211_ATTR_WIPHY_BANDS], bands_remain)
+                       {
+                               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 HT20/HT40 as 11n */
+                               if( caps & (1 << 1) )
+                                       *buf |= IWINFO_80211_N;
+
+                               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 )
+                                       {
+                                               *buf |= IWINFO_80211_B;
+                                               *buf |= IWINFO_80211_G;
+                                       }
+                                       else
+                                       {
+                                               *buf |= IWINFO_80211_A;
+                                       }
+                               }
+                       }
+                       nl80211_free(res);
+               }
+               nl80211_free(req);
+       }
+
+       return *buf ? 0 : -1;
+}
+
 int nl80211_get_mbssid_support(const char *ifname, int *buf)
 {
        /* We assume that multi bssid is always possible */