From 7b6e7f38dab1176a838f67c1c12eb31c805ac792 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Fri, 22 Mar 2024 10:12:43 +0100
Subject: [PATCH] wifi: fix enumeration of 6 GHz channels from wiphy

Command NL80211_CMD_GET_WIPHY without any flag only returns channels
in the 2 GHz and 5 GHz bands, for backwards compatibility with old
userspace tools. To get the full list we need to pass attribute
NL80211_ATTR_SPLIT_WIPHY_DUMP (added in Linux 3.9 released in 2013),
and allow the handler to be called multiple times.

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1500
---
 .../wifi/nm-wifi-utils-nl80211.c              | 94 ++++++++++---------
 1 file changed, 52 insertions(+), 42 deletions(-)

diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
index 3b90d18..3c00898 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
@@ -567,6 +567,7 @@ struct nl80211_device_info {
     int                 phy;
     Nl80211Freq        *freqs;
     int                 num_freqs;
+    int                 num_freqs_alloc;
     guint32             freq;
     guint32             caps;
     gboolean            can_scan;
@@ -610,7 +611,6 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
     struct nlattr              *nl_freq;
     int                         rem_freq;
     int                         rem_band;
-    guint                       num_alloc;
 
 #ifdef NL80211_FREQUENCY_ATTR_NO_IR
     G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR
@@ -622,22 +622,16 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
     if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
         return NL_SKIP;
 
-    if (tb[NL80211_ATTR_WIPHY] == NULL || tb[NL80211_ATTR_WIPHY_BANDS] == NULL)
+    if (!tb[NL80211_ATTR_WIPHY])
         return NL_SKIP;
 
     info->phy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
 
     if (tb[NL80211_ATTR_WIPHY_FREQ])
         info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
-    else
-        info->freq = 0;
 
-    if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) {
+    if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS])
         info->can_scan_ssid = nla_get_u8(tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0;
-    } else {
-        /* old kernel that only had mac80211, so assume it can */
-        info->can_scan_ssid = TRUE;
-    }
 
     if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) {
         struct nlattr *nl_cmd;
@@ -664,51 +658,57 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
         }
     }
 
-    /* Read supported frequencies */
-    num_alloc       = 32;
-    info->num_freqs = 0;
-    info->freqs     = g_new(Nl80211Freq, num_alloc);
+    if (tb[NL80211_ATTR_WIPHY_BANDS]) {
+        /* Read supported frequencies */
 
-    nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-        if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
-            return NL_SKIP;
+        if (!info->freqs) {
+            info->num_freqs       = 0;
+            info->num_freqs_alloc = 32;
+            info->freqs           = g_new(Nl80211Freq, info->num_freqs_alloc);
+        }
 
-        nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) {
-            Nl80211Freq *f;
+        nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
+            if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
+                return NL_SKIP;
 
-            if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0)
+            if (!tb_band[NL80211_BAND_ATTR_FREQS])
                 continue;
 
-            if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
-                continue;
+            nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) {
+                Nl80211Freq *f;
 
-            if (info->num_freqs >= num_alloc) {
-                num_alloc *= 2;
-                info->freqs = g_renew(Nl80211Freq, info->freqs, num_alloc);
-            }
+                if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0)
+                    continue;
+
+                if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
+                    continue;
 
-            f  = &info->freqs[info->num_freqs];
-            *f = (Nl80211Freq){
-                .freq     = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]),
-                .disabled = !!tb_freq[NL80211_FREQUENCY_ATTR_DISABLED],
-                .no_ir    = !!tb_freq[NL80211_FREQUENCY_ATTR_NO_IR],
-            };
+                if (info->num_freqs >= info->num_freqs_alloc) {
+                    info->num_freqs_alloc *= 2;
+                    info->freqs = g_renew(Nl80211Freq, info->freqs, info->num_freqs_alloc);
+                }
 
-            info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID;
+                f  = &info->freqs[info->num_freqs];
+                *f = (Nl80211Freq){
+                    .freq     = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]),
+                    .disabled = !!tb_freq[NL80211_FREQUENCY_ATTR_DISABLED],
+                    .no_ir    = !!tb_freq[NL80211_FREQUENCY_ATTR_NO_IR],
+                };
 
-            if (f->freq >= 2401 && f->freq <= 2495)
-                info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
-            if (f->freq >= 5150 && f->freq < 5950)
-                info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
-            if (f->freq >= 5950 && f->freq <= 7125)
-                info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_6GHZ;
+                info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_VALID;
 
-            info->num_freqs++;
+                if (f->freq >= 2401 && f->freq <= 2495)
+                    info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
+                if (f->freq >= 5150 && f->freq < 5950)
+                    info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
+                if (f->freq >= 5950 && f->freq <= 7125)
+                    info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_6GHZ;
+
+                info->num_freqs++;
+            }
         }
     }
 
-    info->freqs = g_renew(Nl80211Freq, info->freqs, info->num_freqs);
-
     /* Read security/encryption support */
     if (tb[NL80211_ATTR_CIPHER_SUITES]) {
         guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]);
@@ -874,7 +874,10 @@ nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifin
 
     self->phy = -1;
 
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0);
+    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, NLM_F_DUMP);
+    NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP);
+
+    device_info.can_scan_ssid = TRUE;
 
     device_info.self = self;
     if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) {
@@ -882,6 +885,10 @@ nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifin
         return NULL;
     }
 
+    if (device_info.freqs) {
+        device_info.freqs = g_renew(Nl80211Freq, device_info.freqs, device_info.num_freqs);
+    }
+
     if (!device_info.success) {
         _LOGD("NL80211_CMD_GET_WIPHY request indicated failure");
         return NULL;
@@ -915,4 +922,7 @@ nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifin
 
     _LOGD("using nl80211 for Wi-Fi device control");
     return (NMWifiUtils *) g_steal_pointer(&self);
+
+nla_put_failure:
+    g_return_val_if_reached(NULL);
 }
-- 
2.43.0

