added dynamic radio assignment
fixed band steering
This commit is contained in:
@@ -3,3 +3,4 @@ CONFIG_TARGET_mpc85xx_p1020=y
|
||||
CONFIG_TARGET_mpc85xx_p1020_DEVICE_hpe_msm460=y
|
||||
CONFIG_PACKAGE_kmod-tun=m
|
||||
CONFIG_PACKAGE_openuf=m
|
||||
CONFIG_PACKAGE_usteer=m
|
||||
@@ -13,7 +13,7 @@ define Package/openuf
|
||||
SECTION := net
|
||||
CATEGORY := Network
|
||||
TITLE := openUF — UniFi bridge daemon for OpenWrt
|
||||
DEPENDS := +libmbedtls +libuci +libjson-c +kmod-tun
|
||||
DEPENDS := +libmbedtls +libuci +libjson-c +kmod-tun +usteer
|
||||
URL := https://git.ascheu.de/Koda/OpenUniFi
|
||||
endef
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# openUF — C
|
||||
# openUF
|
||||
|
||||
Daemon that makes an OpenWrt router appear as a UniFi AP to UniFi Network controllers.
|
||||
|
||||
@@ -79,7 +79,7 @@ enable_inform = 1 # Enable logging to /var/log/openuf.log if set t
|
||||
|
||||
---
|
||||
|
||||
## Glassary
|
||||
## Glossary
|
||||
|
||||
### TNBU
|
||||
|
||||
|
||||
+7
-2
@@ -228,11 +228,13 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
|
||||
|
||||
for (int i = 0; i < m->radio_map_len; i++) {
|
||||
const uf_radio_map_t *rm = &m->radio_map[i];
|
||||
const char *device = wlan_device_for_band(m, rm->band);
|
||||
if (!device) device = rm->device;
|
||||
|
||||
/* Map "radio0" → "wlan0" by OpenWrt convention */
|
||||
char wlan_iface[32];
|
||||
int ridx = 0;
|
||||
sscanf(rm->device, "radio%d", &ridx);
|
||||
sscanf(device, "radio%d", &ridx);
|
||||
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%d", ridx);
|
||||
|
||||
/* Radio name in the static table */
|
||||
@@ -388,7 +390,10 @@ static struct json_object *build_vap_table(const uf_model_t *m)
|
||||
for (int j = 0; j < m->radio_map_len; j++) {
|
||||
if (strcmp(m->radio_map[j].band, radio) == 0) {
|
||||
int idx = 0;
|
||||
sscanf(m->radio_map[j].device, "radio%d", &idx);
|
||||
const char *device = wlan_device_for_band(
|
||||
m, m->radio_map[j].band);
|
||||
if (!device) device = m->radio_map[j].device;
|
||||
sscanf(device, "radio%d", &idx);
|
||||
if (!ifname || !ifname[0])
|
||||
snprintf(wlan_iface, sizeof(wlan_iface), "phy%d-ap0", idx);
|
||||
radio_stats_t rs;
|
||||
|
||||
+231
-16
@@ -82,6 +82,147 @@
|
||||
#include "wlan.h"
|
||||
#include "ufmodel.h"
|
||||
|
||||
#define MAX_RESOLVED_RADIOS 8
|
||||
|
||||
typedef struct {
|
||||
char device[32];
|
||||
unsigned int bands;
|
||||
} radio_capability_t;
|
||||
|
||||
static const uf_model_t *resolved_model;
|
||||
static char resolved_devices[MAX_RESOLVED_RADIOS][32];
|
||||
|
||||
static unsigned int band_bit(const char *band)
|
||||
{
|
||||
if (!band) return 0;
|
||||
if (!strcmp(band, "ng") || !strcmp(band, "2g")) return 1u;
|
||||
if (!strcmp(band, "na") || !strcmp(band, "5g")) return 2u;
|
||||
if (!strcmp(band, "6g") || !strcmp(band, "6GHz")) return 4u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bit_count(unsigned int value)
|
||||
{
|
||||
int count = 0;
|
||||
while (value) {
|
||||
count += value & 1u;
|
||||
value >>= 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* OpenWrt's generated radioN and phyN indices correspond for mac80211
|
||||
* devices. Read actual frequencies instead of assuming PHY band order. */
|
||||
static unsigned int detect_radio_bands(const char *device)
|
||||
{
|
||||
int phy_index = -1;
|
||||
char command[96];
|
||||
char line[256];
|
||||
unsigned int bands = 0;
|
||||
|
||||
if (!device || sscanf(device, "radio%d", &phy_index) != 1 || phy_index < 0)
|
||||
return 0;
|
||||
|
||||
snprintf(command, sizeof(command), "iw phy phy%d info 2>/dev/null", phy_index);
|
||||
FILE *pipe = popen(command, "r");
|
||||
if (!pipe) return 0;
|
||||
|
||||
while (fgets(line, sizeof(line), pipe)) {
|
||||
char *mhz = strstr(line, " MHz [");
|
||||
if (!mhz || strstr(line, "(disabled)"))
|
||||
continue;
|
||||
|
||||
char *start = mhz;
|
||||
while (start > line &&
|
||||
((start[-1] >= '0' && start[-1] <= '9') || start[-1] == '.'))
|
||||
start--;
|
||||
double frequency = strtod(start, NULL);
|
||||
if (frequency >= 2300.0 && frequency < 3000.0)
|
||||
bands |= 1u;
|
||||
else if (frequency >= 4900.0 && frequency < 5925.0)
|
||||
bands |= 2u;
|
||||
else if (frequency >= 5925.0 && frequency < 7200.0)
|
||||
bands |= 4u;
|
||||
}
|
||||
pclose(pipe);
|
||||
return bands;
|
||||
}
|
||||
|
||||
static void resolve_radio_map(const uf_model_t *model)
|
||||
{
|
||||
if (!model || resolved_model == model)
|
||||
return;
|
||||
|
||||
memset(resolved_devices, 0, sizeof(resolved_devices));
|
||||
resolved_model = model;
|
||||
|
||||
int count = model->radio_map_len;
|
||||
if (count > MAX_RESOLVED_RADIOS)
|
||||
count = MAX_RESOLVED_RADIOS;
|
||||
|
||||
radio_capability_t caps[MAX_RESOLVED_RADIOS] = {0};
|
||||
int used[MAX_RESOLVED_RADIOS] = {0};
|
||||
for (int i = 0; i < count; i++) {
|
||||
snprintf(caps[i].device, sizeof(caps[i].device), "%s",
|
||||
model->radio_map[i].device);
|
||||
caps[i].bands = detect_radio_bands(caps[i].device);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
unsigned int wanted = band_bit(model->radio_map[i].band);
|
||||
int best = -1;
|
||||
int best_band_count = 99;
|
||||
|
||||
for (int j = 0; j < count; j++) {
|
||||
if (used[j] || !(caps[j].bands & wanted))
|
||||
continue;
|
||||
int supported = bit_count(caps[j].bands);
|
||||
if (supported < best_band_count) {
|
||||
best = j;
|
||||
best_band_count = supported;
|
||||
}
|
||||
}
|
||||
|
||||
if (best >= 0) {
|
||||
used[best] = 1;
|
||||
snprintf(resolved_devices[i], sizeof(resolved_devices[i]), "%s",
|
||||
caps[best].device);
|
||||
} else {
|
||||
snprintf(resolved_devices[i], sizeof(resolved_devices[i]), "%s",
|
||||
model->radio_map[i].device);
|
||||
}
|
||||
|
||||
printf("[openuf] Radio mapping: %s -> %s%s\n",
|
||||
model->radio_map[i].band, resolved_devices[i],
|
||||
best >= 0 ? " (detected)" : " (model fallback)");
|
||||
}
|
||||
}
|
||||
|
||||
const char *wlan_device_for_band(const uf_model_t *model, const char *band)
|
||||
{
|
||||
if (!model || !band) return NULL;
|
||||
resolve_radio_map(model);
|
||||
for (int i = 0; i < model->radio_map_len; i++)
|
||||
if (!strcmp(model->radio_map[i].band, band))
|
||||
return i < MAX_RESOLVED_RADIOS && resolved_devices[i][0]
|
||||
? resolved_devices[i] : model->radio_map[i].device;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *wlan_band_for_device(const uf_model_t *model, const char *device)
|
||||
{
|
||||
if (!model || !device) return NULL;
|
||||
resolve_radio_map(model);
|
||||
for (int i = 0; i < model->radio_map_len; i++) {
|
||||
const char *mapped = i < MAX_RESOLVED_RADIOS && resolved_devices[i][0]
|
||||
? resolved_devices[i]
|
||||
: model->radio_map[i].device;
|
||||
if (!strcmp(mapped, device))
|
||||
return model->radio_map[i].band;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ─── Mapeo de seguridad UniFi → OpenWrt UCI ────────────────────── */
|
||||
static const char *sec_to_uci(const char *uf)
|
||||
{
|
||||
@@ -430,6 +571,68 @@ static int ensure_vlan_network(int vid)
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure OpenWrt's steering policy engine. The hostapd 802.11k/v flags
|
||||
* only expose measurements and transition commands; they do not decide when
|
||||
* a station should move. usteer supplies that missing policy loop.
|
||||
*/
|
||||
static int configure_band_steering(int enabled)
|
||||
{
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
struct uci_package *pkg = NULL;
|
||||
if (uci_load(ctx, "usteer", &pkg) != UCI_OK) {
|
||||
printf("[openuf] Cannot load /etc/config/usteer\n");
|
||||
uci_free_context(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct uci_section *settings = NULL;
|
||||
struct uci_element *element;
|
||||
uci_foreach_element(&pkg->sections, element) {
|
||||
struct uci_section *section = uci_to_section(element);
|
||||
if (!strcmp(section->type, "usteer")) {
|
||||
settings = section;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
if (uci_ensure_section(ctx, pkg, "openuf", "usteer") != 0) {
|
||||
uci_unload(ctx, pkg);
|
||||
uci_free_context(ctx);
|
||||
return -1;
|
||||
}
|
||||
settings = uci_lookup_section(ctx, pkg, "openuf");
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
uci_unload(ctx, pkg);
|
||||
uci_free_context(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* A zero interval disables higher-band steering. A zero station-count
|
||||
* threshold is important for small networks: usteer's default of five
|
||||
* otherwise prevents a lone client from being considered. The signal
|
||||
* floor avoids pushing a client onto 5 GHz when that link is too weak.
|
||||
*/
|
||||
UCI_SET(ctx, "usteer", settings->e.name, "band_steering_interval",
|
||||
enabled ? "30000" : "0");
|
||||
UCI_SET(ctx, "usteer", settings->e.name, "band_steering_threshold", "0");
|
||||
UCI_SET(ctx, "usteer", settings->e.name, "band_steering_min_snr", "-65");
|
||||
|
||||
int ok = uci_commit(ctx, &pkg, false) == UCI_OK;
|
||||
uci_unload(ctx, pkg);
|
||||
uci_free_context(ctx);
|
||||
printf("[openuf] Band steering policy %s (usteer)\n",
|
||||
enabled ? "enabled" : "disabled");
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
wlan_clear — remove all VAPs before applying controller ownership
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -836,12 +1039,11 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
const char *radio_band = "";
|
||||
if (json_object_object_get_ex(r, "radio", &v))
|
||||
radio_band = json_object_get_string(v);
|
||||
const char *device_name = "radio0";
|
||||
for (int j = 0; j < model->radio_map_len; j++) {
|
||||
if (!strcmp(model->radio_map[j].band, radio_band)) {
|
||||
device_name = model->radio_map[j].device;
|
||||
break;
|
||||
}
|
||||
const char *device_name = wlan_device_for_band(model, radio_band);
|
||||
if (!device_name) {
|
||||
printf("[openuf] Ignoring settings for unknown radio '%s'\n",
|
||||
radio_band);
|
||||
continue;
|
||||
}
|
||||
wlan_apply_radio(r, device_name);
|
||||
}
|
||||
@@ -886,13 +1088,22 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
printf("[openuf] Disabled %d default OpenWrt VAPs\n",
|
||||
disabled_defaults);
|
||||
|
||||
/* 3. Create VAPs */
|
||||
/* 3. Create VAPs and determine whether any WLAN requests steering. */
|
||||
int band_steering_enabled = 0;
|
||||
if (vt_arr && json_object_is_type(vt_arr, json_type_array)) {
|
||||
int nv = json_object_array_length(vt_arr);
|
||||
for (int i = 0; i < nv; i++) {
|
||||
struct json_object *vap = json_object_array_get_idx(vt_arr, i);
|
||||
if (!vap) continue;
|
||||
|
||||
const char *band_steer_keys[] = {
|
||||
"band_steering", "band_steering_enabled", "steering_enabled"
|
||||
};
|
||||
if (json_boolean_any(vap, band_steer_keys,
|
||||
sizeof(band_steer_keys) /
|
||||
sizeof(band_steer_keys[0])))
|
||||
band_steering_enabled = 1;
|
||||
|
||||
/* A VAP without an explicit band is a model-wide WLAN. */
|
||||
const char *radio_band = NULL;
|
||||
if (json_object_object_get_ex(vap, "radio", &v))
|
||||
@@ -909,7 +1120,9 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
strcmp(model->radio_map[j].band, radio_band))
|
||||
continue;
|
||||
int section_idx = i * model->radio_map_len + j;
|
||||
if (apply_vap(ctx, pkg, vap, model->radio_map[j].device,
|
||||
const char *device = wlan_device_for_band(
|
||||
model, model->radio_map[j].band);
|
||||
if (!device || apply_vap(ctx, pkg, vap, device,
|
||||
model->radio_map[j].band,
|
||||
mac_str, section_idx) != 0) {
|
||||
uci_unload(ctx, pkg);
|
||||
@@ -942,6 +1155,9 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
uci_unload(ctx, pkg);
|
||||
uci_free_context(ctx);
|
||||
|
||||
if (configure_band_steering(band_steering_enabled) != 0)
|
||||
printf("[openuf] Failed to configure the band steering policy\n");
|
||||
|
||||
/*
|
||||
* Reload netifd for generated VLAN devices, then bring the radios up one
|
||||
* at a time. Some dual-ath9k devices intermittently fail their first beacon
|
||||
@@ -952,7 +1168,8 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
system("ubus call network reload >/dev/null 2>&1");
|
||||
for (int i = 0; i < model->radio_map_len; i++) {
|
||||
char command[256];
|
||||
const char *device = model->radio_map[i].device;
|
||||
const char *device = wlan_device_for_band(
|
||||
model, model->radio_map[i].band);
|
||||
|
||||
/* Model radio names are internal constants, but validate defensively. */
|
||||
if (!device ||
|
||||
@@ -997,6 +1214,9 @@ int wlan_apply_config(struct json_object *config_json,
|
||||
if (!radio_up)
|
||||
printf("[openuf] %s failed after 2 start attempts\n", device);
|
||||
}
|
||||
|
||||
/* Restart after hostapd has registered both BSSes on ubus. */
|
||||
system("/etc/init.d/usteer restart >/dev/null 2>&1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1271,13 +1491,8 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
|
||||
if (!device) device = "radio0";
|
||||
|
||||
/* Band of this radio */
|
||||
const char *radio_band = "ng";
|
||||
for (int j = 0; j < model->radio_map_len; j++) {
|
||||
if (!strcmp(model->radio_map[j].device, device)) {
|
||||
radio_band = model->radio_map[j].band;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const char *radio_band = wlan_band_for_device(model, device);
|
||||
if (!radio_band) radio_band = "ng";
|
||||
|
||||
/* Resolve the actual netifd interface (for example phy1-ap0). */
|
||||
char wlan_iface[32];
|
||||
|
||||
@@ -19,6 +19,11 @@ void wlan_clear(void);
|
||||
void wlan_apply_radio(struct json_object *radio_json,
|
||||
const char *device_name);
|
||||
|
||||
/* Resolve the model's logical UniFi band against the bands advertised by
|
||||
* the local PHYs. Falls back to the model mapping when discovery fails. */
|
||||
const char *wlan_device_for_band(const uf_model_t *model, const char *band);
|
||||
const char *wlan_band_for_device(const uf_model_t *model, const char *device);
|
||||
|
||||
/* Apply full config pushed by controller (setstate).
|
||||
* config_json: decoded setstate JSON object
|
||||
* model : model descriptor for radio_map lookup */
|
||||
|
||||
Reference in New Issue
Block a user