Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911eb2577c |
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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. */
|
||||||
|
|||||||
Reference in New Issue
Block a user