Compare commits

..
1 Commits
Author SHA1 Message Date
Koda 911eb2577c added reporting uplink 2026-07-14 19:07:55 +00:00
11 changed files with 70 additions and 741 deletions
+1 -2
View File
@@ -8,7 +8,6 @@ Daemon that makes an OpenWrt router appear as a UniFi AP to UniFi Network contro
| --- | --- | --- | | --- | --- | --- |
| **L2 Discovery** | UDP broadcast + multicast every 10s | `announce.c` → port 10001 | | **L2 Discovery** | UDP broadcast + multicast every 10s | `announce.c` → port 10001 |
| **Adoption** | AES-128-CBC handshake with the controller | `inform.c``handle_response()` | | **Adoption** | AES-128-CBC handshake with the controller | `inform.c``handle_response()` |
| **Remote reboot** | Reboots OpenWrt when requested by the controller | `inform.c``handle_response()` |
| **Firmware spoofing** | Persists and reports the target version requested by an upgrade | `inform.c``handle_response()` | | **Firmware spoofing** | Persists and reports the target version requested by an upgrade | `inform.c``handle_response()` |
| **WiFi Config** | Creates WiFi networks from the controller via UCI | `wlan.c``wlan_apply_config()` | | **WiFi Config** | Creates WiFi networks from the controller via UCI | `wlan.c``wlan_apply_config()` |
| **Band Steering** | 802.11k/v Neighbor Reports + BSS Transition | `wlan.c``apply_vap()` | | **Band Steering** | 802.11k/v Neighbor Reports + BSS Transition | `wlan.c``apply_vap()` |
@@ -77,7 +76,7 @@ apk del wpad-basic-mbedtls && apk add wpad-mbedtls && /etc/init.d/network restar
apk -r del openuf apk -r del openuf
# Install new version # Install new version
apk add openuf-0.4.0-r3.apk --allow-untrusted apk add openuf-0.4.0-r1.apk --allow-untrusted
# Configure # Configure
vi /etc/openuf/openuf.conf vi /etc/openuf/openuf.conf
-11
View File
@@ -167,11 +167,6 @@ int clients_read_wifi(const char *wlan_iface,
if (sscanf(line, " tx bytes: %lld", &llv) == 1) { cur->tx_bytes = llv; continue; } if (sscanf(line, " tx bytes: %lld", &llv) == 1) { cur->tx_bytes = llv; continue; }
if (sscanf(line, " rx packets: %lld", &llv) == 1) { cur->rx_packets = llv; continue; } if (sscanf(line, " rx packets: %lld", &llv) == 1) { cur->rx_packets = llv; continue; }
if (sscanf(line, " tx packets: %lld", &llv) == 1) { cur->tx_packets = llv; continue; } if (sscanf(line, " tx packets: %lld", &llv) == 1) { cur->tx_packets = llv; continue; }
if (sscanf(line, " tx retries: %lld", &llv) == 1) { cur->tx_retries = llv; continue; }
if (sscanf(line, " tx failed: %lld", &llv) == 1) { cur->tx_failed = llv; continue; }
if (sscanf(line, " rx drop misc: %lld", &llv) == 1) { cur->rx_dropped = llv; continue; }
if (sscanf(line, " tx duration: %lld us", &llv) == 1) { cur->tx_duration = llv; continue; }
if (sscanf(line, " rx duration: %lld us", &llv) == 1) { cur->rx_duration = llv; continue; }
/* ── Signal ───────────────────────────────────────────────── */ /* ── Signal ───────────────────────────────────────────────── */
int sig; int sig;
@@ -271,12 +266,6 @@ struct json_object *clients_build_sta_table(const char *wlan_iface,
json_object_object_add(o, "rx_bytes", json_object_new_int64(s->rx_bytes)); json_object_object_add(o, "rx_bytes", json_object_new_int64(s->rx_bytes));
json_object_object_add(o, "tx_packets", json_object_new_int64(s->tx_packets)); json_object_object_add(o, "tx_packets", json_object_new_int64(s->tx_packets));
json_object_object_add(o, "rx_packets", json_object_new_int64(s->rx_packets)); json_object_object_add(o, "rx_packets", json_object_new_int64(s->rx_packets));
json_object_object_add(o, "tx_retries", json_object_new_int64(s->tx_retries));
json_object_object_add(o, "tx_failed", json_object_new_int64(s->tx_failed));
json_object_object_add(o, "tx_dropped", json_object_new_int64(s->tx_failed));
json_object_object_add(o, "rx_dropped", json_object_new_int64(s->rx_dropped));
json_object_object_add(o, "tx_duration", json_object_new_int64(s->tx_duration));
json_object_object_add(o, "rx_duration", json_object_new_int64(s->rx_duration));
json_object_object_add(o, "uptime", json_object_new_int(s->uptime)); json_object_object_add(o, "uptime", json_object_new_int(s->uptime));
json_object_object_add(o, "radio", json_object_new_string(s->radio)); json_object_object_add(o, "radio", json_object_new_string(s->radio));
json_object_object_add(o, "channel", json_object_new_int(s->channel)); json_object_object_add(o, "channel", json_object_new_int(s->channel));
-5
View File
@@ -55,11 +55,6 @@ typedef struct {
long long rx_bytes; long long rx_bytes;
long long tx_packets; long long tx_packets;
long long rx_packets; long long rx_packets;
long long tx_retries;
long long tx_failed;
long long rx_dropped;
long long tx_duration;
long long rx_duration;
int uptime; /* seconds online */ int uptime; /* seconds online */
char radio[8]; /* "ng" / "na" / "6g" */ char radio[8]; /* "ng" / "na" / "6g" */
int channel; int channel;
+1 -1
View File
@@ -2,7 +2,7 @@
#define OPENUF_CONFIG_H #define OPENUF_CONFIG_H
/* ─── Build-time defaults (override with /etc/openuf/openuf.conf) ─── */ /* ─── Build-time defaults (override with /etc/openuf/openuf.conf) ─── */
#define OPENUF_VERSION "0.4.0-r1" #define OPENUF_VERSION "0.3-C"
#define OPENUF_STATE_FILE "/etc/openuf/state.json" #define OPENUF_STATE_FILE "/etc/openuf/state.json"
#define OPENUF_CONF_FILE "/etc/openuf/openuf.conf" #define OPENUF_CONF_FILE "/etc/openuf/openuf.conf"
+28 -288
View File
@@ -155,11 +155,9 @@ static void debug_log_controller_response(struct json_object *response,
The controller shows CPU and RAM in the device view. The controller shows CPU and RAM in the device view.
We read /proc/stat and /proc/meminfo directly. We read /proc/stat and /proc/meminfo directly.
*/ */
static struct json_object *build_sys_stats(int *cpu_percent, static struct json_object *build_sys_stats(void)
double *mem_percent)
{ {
struct json_object *o = json_object_new_object(); struct json_object *o = json_object_new_object();
*mem_percent = 0.0;
mem_stats_t mem; mem_stats_t mem;
if (sysinfo_mem(&mem) == 0) { if (sysinfo_mem(&mem) == 0) {
@@ -178,38 +176,10 @@ static struct json_object *build_sys_stats(int *cpu_percent,
json_object_object_add(o, "mem_buffer", json_object_new_int(0)); json_object_object_add(o, "mem_buffer", json_object_new_int(0));
} }
if (mem.total_kb > 0) /* CPU — delta relative to the previous call (every ~10s gives a good average) */
*mem_percent = 100.0 * (double)(mem.total_kb - mem.free_kb - json_object_object_add(o, "cpu",
mem.buffer_kb - mem.cached_kb) / (double)mem.total_kb; json_object_new_int(sysinfo_cpu_percent()));
FILE *loadavg = fopen("/proc/loadavg", "r");
if (loadavg) {
char one[16], five[16], fifteen[16];
if (fscanf(loadavg, "%15s %15s %15s", one, five, fifteen) == 3) {
json_object_object_add(o, "loadavg_1", json_object_new_string(one));
json_object_object_add(o, "loadavg_5", json_object_new_string(five));
json_object_object_add(o, "loadavg_15", json_object_new_string(fifteen));
}
fclose(loadavg);
}
*cpu_percent = sysinfo_cpu_percent();
return o;
}
static struct json_object *build_system_stats(int cpu_percent,
double mem_percent,
long uptime)
{
struct json_object *o = json_object_new_object();
char value[32];
snprintf(value, sizeof(value), "%.1f", (double)cpu_percent);
json_object_object_add(o, "cpu", json_object_new_string(value));
snprintf(value, sizeof(value), "%.1f", mem_percent);
json_object_object_add(o, "mem", json_object_new_string(value));
snprintf(value, sizeof(value), "%ld", uptime);
json_object_object_add(o, "uptime", json_object_new_string(value));
return o; return o;
} }
@@ -268,52 +238,6 @@ static struct json_object *build_if_table(const uf_model_t *m,
return arr; return arr;
} }
static void radio_runtime_iface(const uf_model_t *m, const char *band,
char *out, size_t out_size)
{
const char *device = wlan_device_for_band(m, band);
int phy_index = 0;
if (device) sscanf(device, "radio%d", &phy_index);
snprintf(out, out_size, "phy%d-ap0", phy_index);
struct json_object *vaps = wlan_get_vap_table(m);
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 *radio_obj, *ifname_obj;
if (json_object_object_get_ex(vap, "radio", &radio_obj) &&
json_object_object_get_ex(vap, "ifname", &ifname_obj) &&
!strcmp(json_object_get_string(radio_obj), band)) {
snprintf(out, out_size, "%s", json_object_get_string(ifname_obj));
break;
}
}
json_object_put(vaps);
}
static struct json_object *build_scan_table(const char *iface)
{
wifi_scan_t scans[MAX_SCAN_RESULTS];
int count = sysinfo_wifi_scan_cache(iface, scans, MAX_SCAN_RESULTS);
struct json_object *arr = json_object_new_array();
for (int i = 0; i < count; i++) {
struct json_object *o = json_object_new_object();
json_object_object_add(o, "age", json_object_new_int(scans[i].age));
json_object_object_add(o, "bssid", json_object_new_string(scans[i].bssid));
json_object_object_add(o, "essid", json_object_new_string(scans[i].essid));
json_object_object_add(o, "freq", json_object_new_int(scans[i].frequency));
json_object_object_add(o, "channel", json_object_new_int(scans[i].channel));
json_object_object_add(o, "signal", json_object_new_int(scans[i].signal));
json_object_object_add(o, "rssi", json_object_new_int(scans[i].rssi));
json_object_object_add(o, "security", json_object_new_string(
scans[i].secured ? "secured" : "open"));
json_object_object_add(o, "is_adhoc", json_object_new_boolean(false));
json_object_object_add(o, "is_ubnt", json_object_new_boolean(false));
json_object_array_add(arr, o);
}
return arr;
}
/* Build the wired uplink object expected by UniFi controllers. /* Build the wired uplink object expected by UniFi controllers.
* The model owns the interface mapping; live state and counters come from * The model owns the interface mapping; live state and counters come from
* sysfs and /proc so third-party upstream switches still get a usable link. */ * sysfs and /proc so third-party upstream switches still get a usable link. */
@@ -387,74 +311,20 @@ static struct json_object *build_uplink(const uf_model_t *m,
Describes the physical capabilities of each radio to the controller. Describes the physical capabilities of each radio to the controller.
The controller uses this to know which frequencies and modes it supports. The controller uses this to know which frequencies and modes it supports.
*/ */
static struct json_object *build_athstats(struct json_object *stats,
const char *radio_name)
{
struct json_object *o = json_object_new_object();
struct json_object *v;
json_object_object_add(o, "name", json_object_new_string(radio_name));
json_object_object_add(o, "noise_floor", json_object_new_int(-95));
json_object_object_add(o, "satisfaction", json_object_new_int(-1));
json_object_object_add(o, "satisfaction_now", json_object_new_int(-1));
json_object_object_add(o, "satisfaction_real", json_object_new_int(-1));
static const char *const fields[] = {
"cu_total", "cu_self_rx", "cu_self_tx", "tx_packets", "tx_retries"
};
for (size_t i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) {
if (stats && json_object_object_get_ex(stats, fields[i], &v))
json_object_object_add(o, fields[i], json_object_get(v));
else
json_object_object_add(o, fields[i], json_object_new_int(0));
}
if (stats && json_object_object_get_ex(stats, "noise", &v)) {
json_object_object_del(o, "noise_floor");
json_object_object_add(o, "noise_floor", json_object_get(v));
}
return o;
}
static void build_radio_table(struct json_object *root, static void build_radio_table(struct json_object *root,
const uf_model_t *m, const uf_model_t *m)
struct json_object *radio_stats)
{ {
struct json_object *arr = json_object_new_array(); struct json_object *arr = json_object_new_array();
for (int i = 0; i < m->radio_table_len; i++) { for (int i = 0; i < m->radio_table_len; i++) {
const uf_radio_t *r = &m->radio_table[i]; const uf_radio_t *r = &m->radio_table[i];
struct json_object *o = json_object_new_object(); struct json_object *o = json_object_new_object();
struct json_object *stats = NULL;
struct json_object *value;
if (radio_stats && i < json_object_array_length(radio_stats))
stats = json_object_array_get_idx(radio_stats, i);
int nss = r->nss;
int tx_antennas = r->nss;
int rx_antennas = r->nss;
if (stats && json_object_object_get_ex(stats, "nss", &value) &&
json_object_get_int(value) > 0)
nss = json_object_get_int(value);
if (stats && json_object_object_get_ex(stats, "num_tx_antennas", &value) &&
json_object_get_int(value) > 0)
tx_antennas = json_object_get_int(value);
if (stats && json_object_object_get_ex(stats, "num_rx_antennas", &value) &&
json_object_get_int(value) > 0)
rx_antennas = json_object_get_int(value);
char mimo[16];
snprintf(mimo, sizeof(mimo), "%dx%d", tx_antennas, rx_antennas);
json_object_object_add(o, "name", json_object_new_string(r->name)); json_object_object_add(o, "name", json_object_new_string(r->name));
json_object_object_add(o, "radio", json_object_new_string(r->radio)); json_object_object_add(o, "radio", json_object_new_string(r->radio));
json_object_object_add(o, "channel", json_object_new_int(r->channel)); json_object_object_add(o, "channel", json_object_new_int(r->channel));
json_object_object_add(o, "ht", json_object_new_string(r->ht)); json_object_object_add(o, "ht", json_object_new_string(r->ht));
json_object_object_add(o, "min_txpower", json_object_new_int(r->min_txpower)); json_object_object_add(o, "min_txpower", json_object_new_int(r->min_txpower));
json_object_object_add(o, "max_txpower", json_object_new_int(r->max_txpower)); json_object_object_add(o, "max_txpower", json_object_new_int(r->max_txpower));
json_object_object_add(o, "nss", json_object_new_int(nss)); json_object_object_add(o, "nss", json_object_new_int(r->nss));
json_object_object_add(o, "max_nss", json_object_new_int(nss));
json_object_object_add(o, "num_tx_antennas", json_object_new_int(tx_antennas));
json_object_object_add(o, "num_rx_antennas", json_object_new_int(rx_antennas));
json_object_object_add(o, "mimo", json_object_new_string(mimo));
json_object_object_add(o, "tx_power", json_object_new_int(r->tx_power)); json_object_object_add(o, "tx_power", json_object_new_int(r->tx_power));
json_object_object_add(o, "radio_caps", json_object_new_int(r->radio_caps)); json_object_object_add(o, "radio_caps", json_object_new_int(r->radio_caps));
json_object_object_add(o, "radio_caps2", json_object_new_int(r->radio_caps2)); json_object_object_add(o, "radio_caps2", json_object_new_int(r->radio_caps2));
@@ -462,24 +332,7 @@ static void build_radio_table(struct json_object *root,
json_object_object_add(o, "he_enabled", json_object_new_boolean(r->he_enabled)); json_object_object_add(o, "he_enabled", json_object_new_boolean(r->he_enabled));
json_object_object_add(o, "builtin_antenna", json_object_new_boolean(true)); json_object_object_add(o, "builtin_antenna", json_object_new_boolean(true));
json_object_object_add(o, "builtin_ant_gain", json_object_new_int(0)); json_object_object_add(o, "builtin_ant_gain", json_object_new_int(0));
json_object_object_add(o, "athstats", build_athstats(stats, r->name));
char iface[32];
radio_runtime_iface(m, r->radio, iface, sizeof(iface));
json_object_object_add(o, "scan_table", build_scan_table(iface));
/*
* Native UniFi device records expose both radio_table[] and a
* top-level radio_<band> alias. Some controller views resolve
* capabilities such as NSS/MIMO through the alias.
*/
char alias[16];
snprintf(alias, sizeof(alias), "radio_%s", r->radio);
json_object_object_add(root, alias, json_object_get(o));
json_object_array_add(arr, o); json_object_array_add(arr, o);
if (protocol_debug_level > 0)
LOG("Protocol radio capability: name=%s band=%s nss=%d tx_chains=%d rx_chains=%d",
r->name, r->radio, nss, tx_antennas, rx_antennas);
} }
json_object_object_add(root, "radio_table", arr); json_object_object_add(root, "radio_table", arr);
} }
@@ -498,8 +351,14 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
for (int i = 0; i < m->radio_map_len; i++) { for (int i = 0; i < m->radio_map_len; i++) {
const uf_radio_map_t *rm = &m->radio_map[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]; char wlan_iface[32];
radio_runtime_iface(m, rm->band, wlan_iface, sizeof(wlan_iface)); int ridx = 0;
sscanf(device, "radio%d", &ridx);
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%d", ridx);
/* Radio name in the static table */ /* Radio name in the static table */
const char *radio_name = (i < m->radio_table_len) const char *radio_name = (i < m->radio_table_len)
@@ -508,32 +367,16 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
? m->radio_table[i].channel : 6; ? m->radio_table[i].channel : 6;
int default_pwr = (i < m->radio_table_len) int default_pwr = (i < m->radio_table_len)
? m->radio_table[i].tx_power : 20; ? m->radio_table[i].tx_power : 20;
int nss = (i < m->radio_table_len)
? m->radio_table[i].nss : 1;
radio_stats_t rs; radio_stats_t rs;
if (sysinfo_radio(wlan_iface, &rs) != 0) { if (sysinfo_radio(wlan_iface, &rs) != 0) {
memset(&rs, 0, sizeof(rs)); memset(&rs, 0, sizeof(rs));
rs.noise = -95; rs.noise = -95;
} }
if (rs.nss > 0) nss = rs.nss;
int tx_antennas = rs.tx_antennas > 0 ? rs.tx_antennas : nss;
int rx_antennas = rs.rx_antennas > 0 ? rs.rx_antennas : nss;
char mimo[16];
snprintf(mimo, sizeof(mimo), "%dx%d", tx_antennas, rx_antennas);
struct json_object *o = json_object_new_object(); struct json_object *o = json_object_new_object();
json_object_object_add(o, "name", json_object_object_add(o, "name",
json_object_new_string(radio_name)); json_object_new_string(radio_name));
json_object_object_add(o, "radio",
json_object_new_string(rm->band));
json_object_object_add(o, "state",
json_object_new_string("RUN"));
json_object_object_add(o, "nss", json_object_new_int(nss));
json_object_object_add(o, "max_nss", json_object_new_int(nss));
json_object_object_add(o, "num_tx_antennas", json_object_new_int(tx_antennas));
json_object_object_add(o, "num_rx_antennas", json_object_new_int(rx_antennas));
json_object_object_add(o, "mimo", json_object_new_string(mimo));
json_object_object_add(o, "channel", json_object_object_add(o, "channel",
json_object_new_int(rs.channel ? rs.channel : default_ch)); json_object_new_int(rs.channel ? rs.channel : default_ch));
json_object_object_add(o, "tx_power", json_object_object_add(o, "tx_power",
@@ -548,16 +391,6 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
json_object_new_int(rs.num_sta)); json_object_new_int(rs.num_sta));
json_object_object_add(o, "noise", json_object_object_add(o, "noise",
json_object_new_int(rs.noise)); json_object_new_int(rs.noise));
json_object_object_add(o, "tx_packets",
json_object_new_int64(rs.tx_packets));
json_object_object_add(o, "tx_retries",
json_object_new_int64(rs.tx_retries));
json_object_object_add(o, "wifi_tx_dropped",
json_object_new_int64(rs.tx_failed));
json_object_object_add(o, "tx_duration",
json_object_new_int64(rs.tx_duration));
json_object_object_add(o, "rx_duration",
json_object_new_int64(rs.rx_duration));
json_object_array_add(arr, o); json_object_array_add(arr, o);
} }
return arr; return arr;
@@ -672,20 +505,6 @@ static struct json_object *build_vap_table(const uf_model_t *m)
if (json_object_object_get_ex(vap, "handoff_suggestions", &v)) if (json_object_object_get_ex(vap, "handoff_suggestions", &v))
handoff_suggestions = json_object_get_boolean(v); handoff_suggestions = json_object_get_boolean(v);
const char *radio_name = radio;
int radio_nss = 1;
int radio_tx_antennas = 1;
int radio_rx_antennas = 1;
for (int j = 0; j < m->radio_table_len; j++) {
if (!strcmp(m->radio_table[j].radio, radio)) {
radio_name = m->radio_table[j].name;
radio_nss = m->radio_table[j].nss;
radio_tx_antennas = radio_nss;
radio_rx_antennas = radio_nss;
break;
}
}
/* Map band → wlan interface and current channel */ /* Map band → wlan interface and current channel */
char wlan_iface[32] = "phy0-ap0"; char wlan_iface[32] = "phy0-ap0";
if (ifname && ifname[0]) if (ifname && ifname[0])
@@ -722,14 +541,8 @@ static struct json_object *build_vap_table(const uf_model_t *m)
/* Calculate tx_power of the corresponding radio */ /* Calculate tx_power of the corresponding radio */
int tx_pwr = 20; int tx_pwr = 20;
radio_stats_t rs2; radio_stats_t rs2;
if (sysinfo_radio(wlan_iface, &rs2) == 0) { if (sysinfo_radio(wlan_iface, &rs2) == 0 && rs2.tx_power)
if (rs2.tx_power) tx_pwr = rs2.tx_power; tx_pwr = rs2.tx_power;
if (rs2.nss > 0) radio_nss = rs2.nss;
if (rs2.tx_antennas > 0) radio_tx_antennas = rs2.tx_antennas;
if (rs2.rx_antennas > 0) radio_rx_antennas = rs2.rx_antennas;
}
char radio_mimo[16];
snprintf(radio_mimo, sizeof(radio_mimo), "%dx%d", radio_tx_antennas, radio_rx_antennas);
struct json_object *o = json_object_new_object(); struct json_object *o = json_object_new_object();
json_object_object_add(o, "essid", json_object_object_add(o, "essid",
@@ -740,15 +553,6 @@ static struct json_object *build_vap_table(const uf_model_t *m)
json_object_new_string(vap_name)); json_object_new_string(vap_name));
json_object_object_add(o, "radio", json_object_object_add(o, "radio",
json_object_new_string(radio)); json_object_new_string(radio));
json_object_object_add(o, "radio_name",
json_object_new_string(radio_name));
json_object_object_add(o, "nss", json_object_new_int(radio_nss));
json_object_object_add(o, "max_nss", json_object_new_int(radio_nss));
json_object_object_add(o, "num_tx_antennas", json_object_new_int(radio_tx_antennas));
json_object_object_add(o, "num_rx_antennas", json_object_new_int(radio_rx_antennas));
json_object_object_add(o, "mimo", json_object_new_string(radio_mimo));
json_object_object_add(o, "state",
json_object_new_string(iface_st.up ? "RUN" : "INIT"));
if (vlan_id > 0) if (vlan_id > 0)
json_object_object_add(o, "vlan_id", json_object_new_int(vlan_id)); json_object_object_add(o, "vlan_id", json_object_new_int(vlan_id));
json_object_object_add(o, "up", json_object_object_add(o, "up",
@@ -781,46 +585,6 @@ static struct json_object *build_vap_table(const uf_model_t *m)
json_object_new_int64(iface_st.rx_dropped)); json_object_new_int64(iface_st.rx_dropped));
json_object_object_add(o, "tx_dropped", json_object_object_add(o, "tx_dropped",
json_object_new_int64(iface_st.tx_dropped)); json_object_new_int64(iface_st.tx_dropped));
long long tx_retries = 0, tx_failed = 0, rx_dropped = 0;
long long signal_sum = 0, ccq_sum = 0;
int signal_count = 0;
int sta_count = json_object_array_length(sta_tbl);
for (int j = 0; j < sta_count; j++) {
struct json_object *station = json_object_array_get_idx(sta_tbl, j);
struct json_object *counter;
if (json_object_object_get_ex(station, "signal", &counter) &&
json_object_get_int(counter) < 0) {
signal_sum += json_object_get_int(counter);
signal_count++;
}
if (json_object_object_get_ex(station, "ccq", &counter))
ccq_sum += json_object_get_int(counter);
if (json_object_object_get_ex(station, "tx_retries", &counter))
tx_retries += json_object_get_int64(counter);
if (json_object_object_get_ex(station, "tx_failed", &counter))
tx_failed += json_object_get_int64(counter);
if (json_object_object_get_ex(station, "rx_dropped", &counter))
rx_dropped += json_object_get_int64(counter);
}
json_object_object_add(o, "avg_client_signal", json_object_new_int(
signal_count ? (int)(signal_sum / signal_count) : 0));
json_object_object_add(o, "num_satisfaction_sta",
json_object_new_int(signal_count));
long long tx_attempts = iface_st.tx_packets + tx_retries;
json_object_object_add(o, "tx_retries", json_object_new_int64(tx_retries));
json_object_object_add(o, "tx_combined_retries",
json_object_new_int64(tx_retries));
json_object_object_add(o, "tx_rts_retries", json_object_new_int(0));
json_object_object_add(o, "tx_total", json_object_new_int64(tx_attempts));
json_object_object_add(o, "tx_success",
json_object_new_int64(iface_st.tx_packets));
json_object_object_add(o, "wifi_tx_attempts",
json_object_new_int64(tx_attempts));
json_object_object_add(o, "wifi_tx_dropped", json_object_new_int64(tx_failed));
json_object_object_add(o, "rx_frags", json_object_new_int64(iface_st.rx_frame));
json_object_object_add(o, "rx_crypts", json_object_new_int(0));
json_object_object_add(o, "rx_nwids", json_object_new_int64(rx_dropped));
/* Only controller-issued ObjectIds are valid in this field. */ /* Only controller-issued ObjectIds are valid in this field. */
if (vap_id) if (vap_id)
json_object_object_add(o, "id", json_object_new_string(vap_id)); json_object_object_add(o, "id", json_object_new_string(vap_id));
@@ -830,9 +594,7 @@ static struct json_object *build_vap_table(const uf_model_t *m)
json_object_object_add(o, "usage", json_object_object_add(o, "usage",
json_object_new_string("user")); json_object_new_string("user"));
json_object_object_add(o, "ccq", json_object_object_add(o, "ccq",
json_object_new_int(signal_count ? (int)(ccq_sum / signal_count) : 0)); json_object_new_int(0));
json_object_object_add(o, "t",
json_object_new_string("vap"));
/* Nested sta_table — clients of THIS VAP */ /* Nested sta_table — clients of THIS VAP */
json_object_object_add(o, "sta_table", sta_tbl); json_object_object_add(o, "sta_table", sta_tbl);
@@ -945,13 +707,8 @@ static char *build_payload(const openuf_state_t *st,
json_object_object_add(root, "country_code", json_object_object_add(root, "country_code",
json_object_new_int(0)); json_object_new_int(0));
/* Memory/load and percentage stats use distinct UniFi schemas. */ /* ── CPU + RAM ──────────────────────────────────────────────── */
int cpu_percent; json_object_object_add(root, "sys_stats", build_sys_stats());
double mem_percent;
json_object_object_add(root, "sys_stats",
build_sys_stats(&cpu_percent, &mem_percent));
json_object_object_add(root, "system-stats",
build_system_stats(cpu_percent, mem_percent, uptime));
/* ── Ethernet interfaces with real counters ──────────────── */ /* ── Ethernet interfaces with real counters ──────────────── */
json_object_object_add(root, "if_table", build_if_table(m, st)); json_object_object_add(root, "if_table", build_if_table(m, st));
@@ -961,11 +718,12 @@ static char *build_payload(const openuf_state_t *st,
json_object_object_add(root, "uplink_table", json_object_object_add(root, "uplink_table",
json_object_new_array()); json_object_new_array());
/* Native informs carry RF counters in radio_table[].athstats. Keep the /* ── Radio capabilities (static, from the model) ─────────────── */
* normalized table too for controller versions that consume it directly. */ build_radio_table(root, m);
struct json_object *radio_stats = build_radio_table_stats(m);
build_radio_table(root, m, radio_stats); /* ── Real-time channel utilization ────────────────────── */
json_object_object_add(root, "radio_table_stats", radio_stats); json_object_object_add(root, "radio_table_stats",
build_radio_table_stats(m));
/* ── Ethernet ports with actual status ───────────────────────── */ /* ── Ethernet ports with actual status ───────────────────────── */
build_port_table(root, m); build_port_table(root, m);
@@ -1109,21 +867,12 @@ static char *parse_packet(const unsigned char *data, size_t data_len,
return copy; return copy;
} }
static void reboot_openwrt(void)
{
LOG("Controller requested an OpenWrt reboot");
int status = system("/sbin/reboot");
if (status != 0)
LOG("OpenWrt reboot command failed with status=%d", status);
}
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
Process JSON command from the controller Process JSON command from the controller
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
_type == "noop" → do nothing _type == "noop" → do nothing
_type == "reboot"reboot OpenWrt _type == "cmd" set-adopt / reboot / reset / locate
_type == "cmd" → set-adopt / legacy reboot / reset / locate
_type == "setstate" → apply radio_table + vap_table via UCI _type == "setstate" → apply radio_table + vap_table via UCI
_type == "setparam" → change a single parameter _type == "setparam" → change a single parameter
*/ */
@@ -1134,8 +883,7 @@ static void handle_response(openuf_state_t *st,
{ {
struct json_object *v; struct json_object *v;
const char *type = "noop"; const char *type = "noop";
if (json_object_object_get_ex(resp, "_type", &v) && if (json_object_object_get_ex(resp, "_type", &v))
json_object_is_type(v, json_type_string))
type = json_object_get_string(v); type = json_object_get_string(v);
LOG("Handling response type: %s", type); LOG("Handling response type: %s", type);
@@ -1168,14 +916,6 @@ static void handle_response(openuf_state_t *st,
return; return;
} }
/* Modern controllers send reboot as a top-level response type rather
* than wrapping it in {"_type":"cmd","cmd":"reboot"}. */
if (!strcmp(type, "reboot")) {
strcpy(action_out, "reboot");
reboot_openwrt();
return;
}
/* ── setparam ────────────────────────────────────────────────── */ /* ── setparam ────────────────────────────────────────────────── */
if (!strcmp(type, "setparam")) { if (!strcmp(type, "setparam")) {
int received_adoption_key = 0; int received_adoption_key = 0;
@@ -1321,12 +1061,12 @@ static void handle_response(openuf_state_t *st,
} else if (!strcmp(cmd, "reboot")) { } else if (!strcmp(cmd, "reboot")) {
strcpy(action_out, "reboot"); strcpy(action_out, "reboot");
reboot_openwrt(); system("reboot &");
} else if (!strcmp(cmd, "reset")) { } else if (!strcmp(cmd, "reset")) {
strcpy(action_out, "reset"); strcpy(action_out, "reset");
system("rm -f " OPENUF_STATE_FILE); system("rm -f " OPENUF_STATE_FILE);
reboot_openwrt(); system("reboot &");
} else if (!strcmp(cmd, "locate")) { } else if (!strcmp(cmd, "locate")) {
/* Blink LED — on OpenWrt: echo 1 > /sys/class/leds/.../trigger */ /* Blink LED — on OpenWrt: echo 1 > /sys/class/leds/.../trigger */
+2 -2
View File
@@ -181,8 +181,8 @@ int main(int argc, char *argv[])
} }
state_save(&state); state_save(&state);
printf("[openuf] Starting version=%s model=%-8s MAC=%s IP=%s\n", printf("[openuf] Starting model=%-8s MAC=%s IP=%s\n",
OPENUF_VERSION, model->model, mac_str, ip_str); model->model, mac_str, ip_str);
printf("[openuf] Controller: %s\n", state.inform_url); printf("[openuf] Controller: %s\n", state.inform_url);
printf("[openuf] Adopted: %s\n", state.adopted ? "yes" : "no"); printf("[openuf] Adopted: %s\n", state.adopted ? "yes" : "no");
printf("[openuf] LLDP available: %s\n", printf("[openuf] LLDP available: %s\n",
+1 -1
View File
@@ -3,7 +3,7 @@
#include <stdbool.h> #include <stdbool.h>
#define OPENUF_CONFIG_SCHEMA 7 #define OPENUF_CONFIG_SCHEMA 6
typedef struct { typedef struct {
bool adopted; bool adopted;
+18 -266
View File
@@ -38,7 +38,6 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <stdbool.h> #include <stdbool.h>
#include <time.h>
#include <net/if.h> #include <net/if.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/socket.h> #include <sys/socket.h>
@@ -201,7 +200,6 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
&tb,&tp,&te,&td,&tf,&tcol,&tcomp,&tcarr); &tb,&tp,&te,&td,&tf,&tcol,&tcomp,&tcarr);
out->rx_bytes = rb; out->rx_packets = rp; out->rx_bytes = rb; out->rx_packets = rp;
out->rx_errors = re; out->rx_dropped = rd; out->rx_errors = re; out->rx_dropped = rd;
out->rx_frame = rframe;
out->rx_multicast= rmulti; out->rx_multicast= rmulti;
out->tx_bytes = tb; out->tx_packets = tp; out->tx_bytes = tb; out->tx_packets = tp;
out->tx_errors = te; out->tx_dropped = td; out->tx_errors = te; out->tx_dropped = td;
@@ -215,46 +213,6 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
WiFi Radio WiFi Radio
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
Survey counters are cumulative. Keep a small per-interface snapshot so
each inform reports utilization during the latest interval rather than an
average since the radio was started.
*/
typedef struct {
char iface[32];
long long active;
long long busy;
long long tx;
long long rx;
long long sta_tx_duration;
long long sta_rx_duration;
struct timespec duration_time;
int duration_valid;
int valid;
} survey_snapshot_t;
#define MAX_SURVEY_SNAPSHOTS 8
static survey_snapshot_t survey_snapshots[MAX_SURVEY_SNAPSHOTS];
static int utilization_percent(long long part, long long total)
{
if (part <= 0 || total <= 0) return 0;
long long value = (part * 100 + total / 2) / total;
if (value < 0) return 0;
if (value > 100) return 100;
return (int)value;
}
static int count_antenna_chains(unsigned int mask)
{
int count = 0;
while (mask) {
count += mask & 1U;
mask >>= 1;
}
return count;
}
/*
1. iw dev wlan0 info → channel and power 1. iw dev wlan0 info → channel and power
Example: Example:
Interface wlan0 Interface wlan0
@@ -281,8 +239,6 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
out->noise = -95; out->noise = -95;
char cmd[128]; char cmd[128];
int current_freq = 0;
int wiphy_index = -1;
/* iw dev <iface> info */ /* iw dev <iface> info */
snprintf(cmd, sizeof(cmd), "iw dev %s info 2>/dev/null", iface); snprintf(cmd, sizeof(cmd), "iw dev %s info 2>/dev/null", iface);
@@ -292,83 +248,30 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
char line[256]; char line[256];
while (fgets(line, sizeof(line), p)) { while (fgets(line, sizeof(line), p)) {
int ch; float mhz; int ch; float mhz;
if (sscanf(line, " channel %d (%f MHz)", &ch, &mhz) == 2) { if (sscanf(line, " channel %d (%f MHz)", &ch, &mhz) == 2)
out->channel = ch; out->channel = ch;
current_freq = (int)(mhz + 0.5f);
}
int index;
if (sscanf(line, " wiphy %d", &index) == 1)
wiphy_index = index;
float tp; float tp;
if (sscanf(line, " txpower %f dBm", &tp) == 1) if (sscanf(line, " txpower %f dBm", &tp) == 1)
out->tx_power = (int)tp; out->tx_power = (int)tp;
} }
pclose(p); pclose(p);
/* Read the physical radio rather than trusting the emulated model. */
if (wiphy_index >= 0) {
snprintf(cmd, sizeof(cmd), "iw phy phy%d info 2>/dev/null", wiphy_index);
p = popen(cmd, "r");
if (p) {
unsigned int available_tx = 0, available_rx = 0;
unsigned int configured_tx = 0, configured_rx = 0;
int max_mcs_nss = 0;
while (fgets(line, sizeof(line), p)) {
unsigned int tx_mask, rx_mask;
if (sscanf(line, " Configured Antennas: TX 0x%x RX 0x%x",
&tx_mask, &rx_mask) == 2) {
configured_tx = tx_mask;
configured_rx = rx_mask;
} else if (sscanf(line, " Available Antennas: TX 0x%x RX 0x%x",
&tx_mask, &rx_mask) == 2) {
available_tx = tx_mask;
available_rx = rx_mask;
}
int streams, top_mcs;
if (sscanf(line, " %d streams: MCS 0-%d", &streams, &top_mcs) == 2 &&
streams > max_mcs_nss)
max_mcs_nss = streams;
if (sscanf(line,
" HT TX/RX MCS rate indexes supported: 0-%d",
&top_mcs) == 1) {
int ht_nss = top_mcs / 8 + 1;
if (ht_nss > max_mcs_nss) max_mcs_nss = ht_nss;
}
}
pclose(p);
out->tx_antennas = count_antenna_chains(
configured_tx ? configured_tx : available_tx);
out->rx_antennas = count_antenna_chains(
configured_rx ? configured_rx : available_rx);
if (out->tx_antennas && out->rx_antennas)
out->nss = out->tx_antennas < out->rx_antennas
? out->tx_antennas : out->rx_antennas;
else
out->nss = max_mcs_nss;
}
}
/* iw dev <iface> survey dump */ /* iw dev <iface> survey dump */
snprintf(cmd, sizeof(cmd), "iw dev %s survey dump 2>/dev/null", iface); snprintf(cmd, sizeof(cmd), "iw dev %s survey dump 2>/dev/null", iface);
p = popen(cmd, "r"); p = popen(cmd, "r");
if (!p) return 0; if (!p) return 0;
long long active=0, busy=0, tx_t=0, rx_t=0; long long active=0, busy=0, tx_t=0, rx_t=0;
int selected = 0; int in_use = 0;
while (fgets(line, sizeof(line), p)) { while (fgets(line, sizeof(line), p)) {
int survey_freq; if (strstr(line, "[in use]")) {
if (sscanf(line, " frequency: %d MHz", &survey_freq) == 1) { in_use = 1; active=busy=tx_t=rx_t=0; continue;
/* Some drivers omit the optional [in use] marker, especially on }
* the secondary radio. Match the frequency reported by iw info. */ if (!in_use) continue;
selected = strstr(line, "[in use]") != NULL || /* New frequency without [in use] resets the block */
(current_freq > 0 && survey_freq == current_freq); if (strstr(line, "frequency:") && !strstr(line, "[in use]")) {
if (selected) in_use = 0; continue;
active=busy=tx_t=rx_t=0;
continue;
} }
if (!selected) continue;
float noise; long long val; float noise; long long val;
if (sscanf(line, " noise: %f dBm", &noise) == 1) out->noise = (int)noise; if (sscanf(line, " noise: %f dBm", &noise) == 1) out->noise = (int)noise;
if (sscanf(line, " channel active time: %lld ms", &val) == 1) active = val; if (sscanf(line, " channel active time: %lld ms", &val) == 1) active = val;
@@ -378,169 +281,18 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
} }
pclose(p); pclose(p);
long long sample_active = active; if (active > 0) {
long long sample_busy = busy; out->cu_total = (int)(busy * 100 / active);
long long sample_tx = tx_t; out->cu_self_tx = (int)(tx_t * 100 / active);
long long sample_rx = rx_t; out->cu_self_rx = (int)(rx_t * 100 / active);
survey_snapshot_t *snapshot = NULL;
survey_snapshot_t *free_slot = NULL;
for (int i = 0; i < MAX_SURVEY_SNAPSHOTS; i++) {
if (survey_snapshots[i].valid &&
!strcmp(survey_snapshots[i].iface, iface)) {
snapshot = &survey_snapshots[i];
break;
}
if (!survey_snapshots[i].valid && !free_slot)
free_slot = &survey_snapshots[i];
}
if (!snapshot) snapshot = free_slot;
if (snapshot && snapshot->valid && active > snapshot->active &&
busy >= snapshot->busy && tx_t >= snapshot->tx && rx_t >= snapshot->rx) {
sample_active = active - snapshot->active;
sample_busy = busy - snapshot->busy;
sample_tx = tx_t - snapshot->tx;
sample_rx = rx_t - snapshot->rx;
} }
if (snapshot && active > 0) { /* Number of associated clients */
snprintf(snapshot->iface, sizeof(snapshot->iface), "%s", iface); snprintf(cmd, sizeof(cmd),
snapshot->active = active; "iw dev %s station dump 2>/dev/null | grep -c '^Station'",
snapshot->busy = busy; iface);
snapshot->tx = tx_t;
snapshot->rx = rx_t;
snapshot->valid = 1;
}
if (sample_active > 0) {
out->cu_total = utilization_percent(sample_busy, sample_active);
out->cu_self_tx = utilization_percent(sample_tx, sample_active);
out->cu_self_rx = utilization_percent(sample_rx, sample_active);
}
/* Associated clients and counters exposed by nl80211. */
snprintf(cmd, sizeof(cmd), "iw dev %s station dump 2>/dev/null", iface);
p = popen(cmd, "r"); p = popen(cmd, "r");
if (p) { if (p) { fscanf(p, "%d", &out->num_sta); pclose(p); }
while (fgets(line, sizeof(line), p)) {
long long val;
if (!strncmp(line, "Station ", 8)) {
out->num_sta++;
} else if (sscanf(line, " tx packets: %lld", &val) == 1) {
out->tx_packets += val;
} else if (sscanf(line, " tx retries: %lld", &val) == 1) {
out->tx_retries += val;
} else if (sscanf(line, " tx failed: %lld", &val) == 1) {
out->tx_failed += val;
} else if (sscanf(line, " tx duration: %lld us", &val) == 1) {
out->tx_duration += val;
} else if (sscanf(line, " rx duration: %lld us", &val) == 1) {
out->rx_duration += val;
}
}
pclose(p);
}
/* Some drivers expose no survey busy time on their secondary radio but
* do expose per-station airtime durations. Use those interval counters as
* a conservative "This AP" fallback; interference remains zero because
* station data cannot measure neighboring transmitters. */
struct timespec now;
if (snapshot && clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
long long elapsed_us = 0;
if (snapshot->duration_valid) {
elapsed_us = (now.tv_sec - snapshot->duration_time.tv_sec) * 1000000LL +
(now.tv_nsec - snapshot->duration_time.tv_nsec) / 1000LL;
}
if (snapshot->duration_valid && elapsed_us >= 1000000LL &&
out->tx_duration >= snapshot->sta_tx_duration &&
out->rx_duration >= snapshot->sta_rx_duration &&
out->cu_total == 0) {
long long tx_delta = out->tx_duration - snapshot->sta_tx_duration;
long long rx_delta = out->rx_duration - snapshot->sta_rx_duration;
out->cu_self_tx = utilization_percent(tx_delta, elapsed_us);
out->cu_self_rx = utilization_percent(rx_delta, elapsed_us);
out->cu_total = out->cu_self_tx + out->cu_self_rx;
if (out->cu_total > 100) out->cu_total = 100;
}
/* sysinfo_radio() is called more than once while building an inform.
* Ignore sub-second calls so they do not replace the interval base. */
if (!snapshot->duration_valid || elapsed_us >= 1000000LL) {
if (!snapshot->valid) {
snprintf(snapshot->iface, sizeof(snapshot->iface), "%s", iface);
snapshot->valid = 1;
}
snapshot->sta_tx_duration = out->tx_duration;
snapshot->sta_rx_duration = out->rx_duration;
snapshot->duration_time = now;
snapshot->duration_valid = 1;
}
}
return 0; return 0;
} }
static int frequency_to_channel(int frequency)
{
if (frequency == 2484) return 14;
if (frequency >= 2412 && frequency <= 2472)
return (frequency - 2407) / 5;
if (frequency >= 5000 && frequency <= 5895)
return (frequency - 5000) / 5;
if (frequency >= 5955 && frequency <= 7115)
return (frequency - 5950) / 5;
return 0;
}
int sysinfo_wifi_scan_cache(const char *iface, wifi_scan_t *out, int max_out)
{
if (!iface || !out || max_out <= 0) return 0;
char cmd[128];
snprintf(cmd, sizeof(cmd), "iw dev %s scan dump 2>/dev/null", iface);
FILE *p = popen(cmd, "r");
if (!p) return 0;
int count = 0;
wifi_scan_t *cur = NULL;
char line[512];
while (fgets(line, sizeof(line), p)) {
char bssid[32];
if (sscanf(line, "BSS %31[^ (]", bssid) == 1) {
if (count >= max_out) {
cur = NULL;
continue;
}
cur = &out[count++];
memset(cur, 0, sizeof(*cur));
snprintf(cur->bssid, sizeof(cur->bssid), "%s", bssid);
continue;
}
if (!cur) continue;
int value;
float signal;
char essid[64];
if (sscanf(line, " freq: %d", &value) == 1) {
cur->frequency = value;
cur->channel = frequency_to_channel(value);
} else if (sscanf(line, " signal: %f dBm", &signal) == 1) {
cur->signal = (int)signal;
value = (cur->signal + 100) * 2;
cur->rssi = value < 0 ? 0 : value > 100 ? 100 : value;
} else if (sscanf(line, " last seen: %d ms ago", &value) == 1) {
cur->age = value / 1000;
} else if (sscanf(line, " SSID: %63[^\n]", essid) == 1) {
snprintf(cur->essid, sizeof(cur->essid), "%s", essid);
} else if (strstr(line, "capability:") && strstr(line, "Privacy")) {
cur->secured = true;
} else if (strstr(line, "RSN:") || strstr(line, "WPA:")) {
cur->secured = true;
}
}
pclose(p);
return count;
}
-25
View File
@@ -49,7 +49,6 @@ typedef struct {
long long tx_errors; long long tx_errors;
long long rx_dropped; long long rx_dropped;
long long tx_dropped; long long tx_dropped;
long long rx_frame;
long long rx_multicast; long long rx_multicast;
} iface_stats_t; } iface_stats_t;
@@ -66,33 +65,9 @@ typedef struct {
int cu_self_rx; /* % time spent receiving */ int cu_self_rx; /* % time spent receiving */
int num_sta; int num_sta;
int noise; /* dBm */ int noise; /* dBm */
int nss; /* usable spatial streams */
int tx_antennas; /* configured TX chains */
int rx_antennas; /* configured RX chains */
long long tx_packets;
long long tx_retries;
long long tx_failed;
long long tx_duration; /* microseconds */
long long rx_duration; /* microseconds */
} radio_stats_t; } radio_stats_t;
/* iface: "wlan0", "wlan1" */ /* iface: "wlan0", "wlan1" */
int sysinfo_radio(const char *iface, radio_stats_t *out); int sysinfo_radio(const char *iface, radio_stats_t *out);
#define MAX_SCAN_RESULTS 128
typedef struct {
char bssid[32];
char essid[64];
int frequency;
int channel;
int signal; /* dBm */
int rssi; /* controller-compatible quality, 0-100 */
int age; /* seconds since last seen */
bool secured;
} wifi_scan_t;
/* Read the kernel's cached BSS list without starting a disruptive scan. */
int sysinfo_wifi_scan_cache(const char *iface, wifi_scan_t *out, int max_out);
#endif /* OPENUF_SYSINFO_H */ #endif /* OPENUF_SYSINFO_H */
+17 -136
View File
@@ -737,84 +737,6 @@ 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)
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
@@ -826,8 +748,7 @@ static void select_htmode(const char *device_name, const char *radio_band,
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;
@@ -841,12 +762,11 @@ 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)) {
radio_band = json_object_get_string(v); const char *radio = json_object_get_string(v);
const char *band = !strcmp(radio_band, "ng") ? "2g" : const char *band = !strcmp(radio, "ng") ? "2g" :
!strcmp(radio_band, "na") ? "5g" : !strcmp(radio, "na") ? "5g" :
!strcmp(radio_band, "6g") ? "6g" : NULL; !strcmp(radio, "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);
@@ -856,20 +776,17 @@ void wlan_apply_radio(struct json_object *radio_json,
} }
} }
if (json_object_object_get_ex(radio_json, "ht", &v)) { #define RP(key, uci_opt) \
const char *requested = json_object_get_string(v); if (json_object_object_get_ex(radio_json, key, &v)) { \
char htmode[16]; snprintf(path, sizeof(path), "wireless.%s.%s=%s", \
select_htmode(device_name, radio_band, requested, force_wifi4, device_name, uci_opt, json_object_get_string(v)); \
htmode, sizeof(htmode)); struct uci_ptr ptr; \
snprintf(path, sizeof(path), "wireless.%s.htmode=%s", if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK) \
device_name, htmode); uci_set(ctx, &ptr); \
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);
@@ -898,6 +815,8 @@ 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);
@@ -1191,37 +1110,6 @@ 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
═══════════════════════════════════════════════════════════════════ ═══════════════════════════════════════════════════════════════════
@@ -1279,8 +1167,7 @@ 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));
} }
} }
@@ -1589,12 +1476,6 @@ 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,
+2 -4
View File
@@ -15,11 +15,9 @@ 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. */