enable wifi version steering (#28)

Reviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
2026-07-14 23:38:46 +01:00
parent b52a0a165d
commit 37c286e756
3 changed files with 141 additions and 20 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
#include <stdbool.h> #include <stdbool.h>
#define OPENUF_CONFIG_SCHEMA 6 #define OPENUF_CONFIG_SCHEMA 7
typedef struct { typedef struct {
bool adopted; bool adopted;
+136 -17
View File
@@ -737,6 +737,84 @@ void wlan_clear(void)
uci_free_context(ctx); uci_free_context(ctx);
} }
enum wifi_standard {
WIFI_STANDARD_UNKNOWN = 0,
WIFI_STANDARD_4 = 4,
WIFI_STANDARD_5 = 5,
WIFI_STANDARD_6 = 6,
WIFI_STANDARD_7 = 7,
};
/* Return the newest 802.11 generation advertised by the local PHY. */
static enum wifi_standard radio_max_standard(const char *device_name)
{
int phy_index = -1;
char command[96];
char line[256];
enum wifi_standard standard = WIFI_STANDARD_UNKNOWN;
if (!device_name || sscanf(device_name, "radio%d", &phy_index) != 1 ||
phy_index < 0)
return WIFI_STANDARD_UNKNOWN;
snprintf(command, sizeof(command), "iw phy phy%d info 2>/dev/null",
phy_index);
FILE *pipe = popen(command, "r");
if (!pipe)
return WIFI_STANDARD_UNKNOWN;
while (fgets(line, sizeof(line), pipe)) {
if (strstr(line, "EHT Iftypes"))
standard = WIFI_STANDARD_7;
else if (standard < WIFI_STANDARD_6 && strstr(line, "HE Iftypes"))
standard = WIFI_STANDARD_6;
else if (standard < WIFI_STANDARD_5 &&
strstr(line, "VHT Capabilities"))
standard = WIFI_STANDARD_5;
else if (standard < WIFI_STANDARD_4 && strstr(line, "Capabilities:"))
standard = WIFI_STANDARD_4;
}
pclose(pipe);
return standard;
}
/* Keep the controller-selected width but use the newest PHY generation. */
static void select_htmode(const char *device_name, const char *radio_band,
const char *requested, int force_wifi4,
char *result, size_t result_size)
{
enum wifi_standard standard = radio_max_standard(device_name);
int width = 20;
const char *number = requested;
while (number && *number && (*number < '0' || *number > '9'))
number++;
if (number && *number)
width = atoi(number);
if (force_wifi4) {
/* 802.11n cannot use 80 MHz or wider channels. */
standard = WIFI_STANDARD_4;
if (width > 40)
width = 40;
} else if (standard == WIFI_STANDARD_5 && radio_band &&
(!strcmp(radio_band, "ng") || !strcmp(radio_band, "2g"))) {
/* OpenWrt does not use VHT modes on the 2.4 GHz band. */
standard = WIFI_STANDARD_4;
}
const char *prefix = standard == WIFI_STANDARD_7 ? "EHT" :
standard == WIFI_STANDARD_6 ? "HE" :
standard == WIFI_STANDARD_5 ? "VHT" :
standard == WIFI_STANDARD_4 ? "HT" : NULL;
if (!prefix) {
snprintf(result, result_size, "%s", requested ? requested : "HT20");
return;
}
snprintf(result, result_size, "%s%d", prefix, width);
}
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
wlan_apply_radio — apply radio config (channel, HT, power) wlan_apply_radio — apply radio config (channel, HT, power)
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
@@ -748,7 +826,8 @@ void wlan_clear(void)
min_rssi → not mapped to UCI (requires an external daemon) min_rssi → not mapped to UCI (requires an external daemon)
*/ */
void wlan_apply_radio(struct json_object *radio_json, void wlan_apply_radio(struct json_object *radio_json,
const char *device_name) const char *device_name,
int force_wifi4)
{ {
struct uci_context *ctx = uci_alloc_context(); struct uci_context *ctx = uci_alloc_context();
if (!ctx) return; if (!ctx) return;
@@ -762,11 +841,12 @@ void wlan_apply_radio(struct json_object *radio_json,
char path[256]; char path[256];
/* Map UniFi band names to OpenWrt mac80211 band names. */ /* Map UniFi band names to OpenWrt mac80211 band names. */
const char *radio_band = NULL;
if (json_object_object_get_ex(radio_json, "radio", &v)) { if (json_object_object_get_ex(radio_json, "radio", &v)) {
const char *radio = json_object_get_string(v); radio_band = json_object_get_string(v);
const char *band = !strcmp(radio, "ng") ? "2g" : const char *band = !strcmp(radio_band, "ng") ? "2g" :
!strcmp(radio, "na") ? "5g" : !strcmp(radio_band, "na") ? "5g" :
!strcmp(radio, "6g") ? "6g" : NULL; !strcmp(radio_band, "6g") ? "6g" : NULL;
if (band) { if (band) {
snprintf(path, sizeof(path), "wireless.%s.band=%s", snprintf(path, sizeof(path), "wireless.%s.band=%s",
device_name, band); device_name, band);
@@ -776,17 +856,20 @@ void wlan_apply_radio(struct json_object *radio_json,
} }
} }
#define RP(key, uci_opt) \ if (json_object_object_get_ex(radio_json, "ht", &v)) {
if (json_object_object_get_ex(radio_json, key, &v)) { \ const char *requested = json_object_get_string(v);
snprintf(path, sizeof(path), "wireless.%s.%s=%s", \ char htmode[16];
device_name, uci_opt, json_object_get_string(v)); \ select_htmode(device_name, radio_band, requested, force_wifi4,
struct uci_ptr ptr; \ htmode, sizeof(htmode));
if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK) \ snprintf(path, sizeof(path), "wireless.%s.htmode=%s",
uci_set(ctx, &ptr); \ device_name, htmode);
struct uci_ptr ptr;
if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK)
uci_set(ctx, &ptr);
printf("[openuf] Radio %s standard: %s%s\n", device_name, htmode,
force_wifi4 ? " (Force WiFi 4)" : " (newest supported)");
} }
RP("ht", "htmode");
/* Channel: 0 = auto in UniFi */ /* Channel: 0 = auto in UniFi */
if (json_object_object_get_ex(radio_json, "channel", &v)) { if (json_object_object_get_ex(radio_json, "channel", &v)) {
int ch = json_object_get_int(v); int ch = json_object_get_int(v);
@@ -815,8 +898,6 @@ void wlan_apply_radio(struct json_object *radio_json,
if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK) if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK)
uci_set(ctx, &ptr); uci_set(ctx, &ptr);
#undef RP
uci_commit(ctx, &pkg, false); uci_commit(ctx, &pkg, false);
uci_unload(ctx, pkg); uci_unload(ctx, pkg);
uci_free_context(ctx); uci_free_context(ctx);
@@ -1110,6 +1191,37 @@ static int apply_vap(struct uci_context *ctx,
return 0; return 0;
} }
/* True when a UniFi WLAN on this band explicitly requests WiFi 4 mode. */
static int force_wifi4_for_band(struct json_object *vaps, const char *band)
{
if (!vaps || !json_object_is_type(vaps, json_type_array))
return 0;
const char *force_keys[] = {
"iot", "force_wifi4", "force_wifi4_mode"
};
int count = json_object_array_length(vaps);
for (int i = 0; i < count; i++) {
struct json_object *vap = json_object_array_get_idx(vaps, i);
struct json_object *value;
if (!vap || !json_boolean_any(vap, force_keys,
sizeof(force_keys) /
sizeof(force_keys[0])))
continue;
const char *vap_band = NULL;
if (json_object_object_get_ex(vap, "radio", &value))
vap_band = json_object_get_string(value);
if (!vap_band || !vap_band[0] || !strcmp(vap_band, "both") ||
!strcmp(vap_band, "all") || !strcmp(vap_band, band) ||
(!strcmp(vap_band, "2g") && !strcmp(band, "ng")) ||
(!strcmp(vap_band, "5g") && !strcmp(band, "na")) ||
(!strcmp(vap_band, "6GHz") && !strcmp(band, "6g")))
return 1;
}
return 0;
}
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
wlan_apply_config — apply the controller's full configuration wlan_apply_config — apply the controller's full configuration
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
@@ -1167,7 +1279,8 @@ int wlan_apply_config(struct json_object *config_json,
radio_band); radio_band);
continue; continue;
} }
wlan_apply_radio(r, device_name); wlan_apply_radio(r, device_name,
force_wifi4_for_band(vt_arr, radio_band));
} }
} }
@@ -1476,6 +1589,12 @@ int wlan_apply_system_cfg(const char *system_cfg,
band = "na"; band = "na";
json_object_object_add(vap, "radio", json_object_new_string(band)); json_object_object_add(vap, "radio", json_object_new_string(band));
/* UniFi's legacy Force WiFi 4 mode is carried as wireless.N.iot. */
snprintf(key, sizeof(key), "wireless.%d.iot", i);
if (system_cfg_get(system_cfg, key, value, sizeof(value)))
json_object_object_add(vap, "iot", json_object_new_boolean(
feature_text_enabled(value)));
char passphrase[256] = {0}; char passphrase[256] = {0};
snprintf(key, sizeof(key), "aaa.%d.wpa.psk", i); snprintf(key, sizeof(key), "aaa.%d.wpa.psk", i);
int has_passphrase = system_cfg_get(system_cfg, key, passphrase, int has_passphrase = system_cfg_get(system_cfg, key, passphrase,
+4 -2
View File
@@ -15,9 +15,11 @@ void wlan_clear(void);
/* Apply radio-level settings from a UniFi radio_table entry. /* Apply radio-level settings from a UniFi radio_table entry.
* radio_json : JSON object with fields: channel, ht, tx_power * radio_json : JSON object with fields: channel, ht, tx_power
* device_name: OpenWrt radio device ("radio0", "radio1") */ * device_name: OpenWrt radio device ("radio0", "radio1")
* force_wifi4: cap the radio at 802.11n when requested by UniFi */
void wlan_apply_radio(struct json_object *radio_json, void wlan_apply_radio(struct json_object *radio_json,
const char *device_name); const char *device_name,
int force_wifi4);
/* Resolve the model's logical UniFi band against the bands advertised by /* Resolve the model's logical UniFi band against the bands advertised by
* the local PHYs. Falls back to the model mapping when discovery fails. */ * the local PHYs. Falls back to the model mapping when discovery fails. */