Translated all files to ENG

This commit is contained in:
2026-07-12 00:43:46 +00:00
parent fb542777ca
commit b821e6f213
14 changed files with 317 additions and 307 deletions
+19 -19
View File
@@ -1,26 +1,26 @@
/*
* openuf - announce.c
*
* Implementa el protocolo de descubrimiento UDP de UniFi (puerto 10001).
* Implements the UniFi UDP discovery protocol (port 10001).
*
* ── Destinos ─────────────────────────────────────────────────────────
* El protocolo especifica que los paquetes de anuncio se envían a DOS destinos:
* ── Discovery Targets ────────────────────────────────────────────────
* The protocol specifies that discovery packets are sent to TWO targets:
* 1. Broadcast: 255.255.255.255:10001
* 2. Multicast: 233.89.188.1:10001 ← requerido para redes con multicast
* 2. Multicast: 233.89.188.1:10001 ← required on multicast-enabled networks
*
* El controlador UniFi escucha en ambas direcciones.
* Usar sólo broadcast puede fallar en redes donde el broadcast está filtrado.
* The UniFi controller listens on both addresses.
* Using broadcast alone may fail on networks where broadcast traffic is filtered.
*
* ── Formato del paquete ──────────────────────────────────────────────
* Header: [0x02][0x06][0x00][total_payload_len] (4 bytes fijos)
* ── Packet Format ────────────────────────────────────────────────────
* Header: [0x02][0x06][0x00][total_payload_len] (fixed 4-byte header)
* TLVs: [type:1][len_hi:1][len_lo:1][value:len]
*
* ── Modelo U6 InWall ─────────────────────────────────────────────────
* Se emula este modelo específicamente porque:
* - Tiene 5 puertos GbE (eth0-eth4): cubre la mayoría de routers OpenWrt
* - Soporta WiFi 6 (802.11ax) en 2.4 GHz y 5 GHz
* - Tiene PoE passthrough (útil para redes de campus)
* - Es un modelo actual y bien soportado por el controlador
* ── U6 In-Wall Model ─────────────────────────────────────────────────
* This model is emulated because:
* - It provides 5 Gigabit Ethernet ports (eth0-eth4), covering most OpenWrt routers
* - Supports WiFi 6 (802.11ax) on both 2.4 GHz and 5 GHz bands
* - Includes PoE passthrough, useful for campus and enterprise deployments
* - Is a current model with excellent UniFi Controller compatibility
*/
#include <stdio.h>
@@ -212,9 +212,9 @@ int announce_init(announce_ctx_t *ctx,
};
bind(ctx->sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
/* ── Socket para multicast 233.89.188.1 ────────────────────── */
/* El controlador UniFi también escucha en este grupo multicast.
* Esto es necesario cuando broadcast está filtrado en la red. */
/* ── Multicast Socket (233.89.188.1) ────────────────────────── */
/* The UniFi Controller also listens on this multicast group,
* allowing discovery even when broadcast traffic is filtered. */
ctx->sockfd_mcast = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ctx->sockfd_mcast >= 0) {
int ttl = 1; /* TTL=1: no cruzar router */
@@ -241,7 +241,7 @@ int announce_send(announce_ctx_t *ctx)
int ret = 0;
/* ── Envío 1: Broadcast 255.255.255.255:10001 ─────────────── */
/* ── Sending 1: Broadcast 255.255.255.255:10001 ─────────────── */
struct sockaddr_in dest_bcast = {
.sin_family = AF_INET,
.sin_port = htons(ANNOUNCE_PORT),
@@ -253,7 +253,7 @@ int announce_send(announce_ctx_t *ctx)
ret = -1;
}
/* ── Envío 2: Multicast 233.89.188.1:10001 ────────────────── */
/* ── Sending 2: Multicast 233.89.188.1:10001 ────────────────── */
if (ctx->sockfd_mcast >= 0) {
struct sockaddr_in dest_mcast = {
.sin_family = AF_INET,
+25 -22
View File
@@ -1,11 +1,13 @@
/*
* openuf - clients.c
*
* Enumera clientes para el payload inform → sta_table.
*
* Enumerates clients for the inform payload → sta_table.
*
* ── Parseo de iw dev station dump ───────────────────────────────────
* ── Parsing `iw dev station dump` Output ────────────────────────────
*
* The output is organized into one block per client:
*
* La salida tiene bloques por cliente:
*
* Station aa:bb:cc:dd:ee:ff (on wlan0)
* inactive time: 120 ms
@@ -18,16 +20,16 @@
* rx bitrate: 108.0 MBit/s
* connected time: 1800 seconds
*
* Detectamos el inicio de cada cliente con "Station XX:XX:..." y
* rellenamos los campos hasta encontrar el siguiente cliente.
* We detect the start of each client by looking for "Station XX:XX:..."
* and populate its fields until the next client entry is encountered.
*
* ── ARP: /proc/net/arp ─────────────────────────────────────────────
*
* IP HW type Flags HW addr Mask Device
* 192.168.1.x 0x1 0x2 aa:bb:cc:dd:ee:ff * br-lan
*
* Flags 0x2 = entrada completa (reachable).
* Flags 0x0 = incompleta (no responde ARP), ignorar.
* Flags 0x2 = complete entry (reachable).
* Flags 0x0 = incomplete (no ARP responce), ignore.
*/
#define _GNU_SOURCE
@@ -39,7 +41,7 @@
#include "clients.h"
/* ─── Normalizar MAC a minúsculas ─────────────────────────────────── */
/* ─── Convert MAC address to lowercase ─────────────────────────────────── */
static void mac_lower(const char *src, char *dst, size_t sz)
{
for (size_t i = 0; src[i] && i < sz-1; i++)
@@ -114,7 +116,7 @@ int clients_mac_to_hostname(const char *mac, char *out, size_t sz)
return -1;
}
/* ─── Parsear tasa de bits "144.4 MBit/s ..." → kbps ───────────── */
/* ─── Parse bitrate "144.4 MBit/s ..." → kbps ───────────── */
static long parse_rate_kbps(const char *s)
{
float r = 0;
@@ -144,7 +146,7 @@ int clients_read_wifi(const char *wlan_iface,
while (fgets(line, sizeof(line), p)) {
line[strcspn(line, "\r\n")] = '\0';
/* ── Nueva estación ──────────────────────────────────────── */
/* ── New station ──────────────────────────────────────── */
char mac[32], on_iface[32];
if (sscanf(line, "Station %31s (on %31[^)])", mac, on_iface) == 2) {
if (count >= max_out) break;
@@ -159,14 +161,14 @@ int clients_read_wifi(const char *wlan_iface,
}
if (!cur) continue;
/* ── Contadores ──────────────────────────────────────────── */
/* ── Counters ──────────────────────────────────────────── */
long long llv;
if (sscanf(line, " rx bytes: %lld", &llv) == 1) { cur->rx_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, " tx packets: %lld", &llv) == 1) { cur->tx_packets = llv; continue; }
/* ── Sal ───────────────────────────────────────────────── */
/* ── Signal ───────────────────────────────────────────────── */
int sig;
if (sscanf(line, " signal: %d", &sig) == 1) { cur->signal = sig; continue; }
@@ -179,7 +181,7 @@ int clients_read_wifi(const char *wlan_iface,
cur->rx_rate = parse_rate_kbps(rest); continue;
}
/* ── Tiempo conectado ────────────────────────────────────── */
/* ── Connection time ────────────────────────────────────── */
int upt;
if (sscanf(line, " connected time: %d seconds", &upt) == 1) {
cur->uptime = upt; continue;
@@ -187,7 +189,7 @@ int clients_read_wifi(const char *wlan_iface,
}
pclose(p);
/* ── Enriquecer: IP, hostname, rssi, CCQ ─────────────────────── */
/* ── Enrich with IP address, hostname, RSSI, and CCQ.─────────────────────── */
for (int i = 0; i < count; i++) {
sta_info_t *s = &out[i];
clients_mac_to_ip(s->mac, s->ip, sizeof(s->ip));
@@ -195,14 +197,15 @@ int clients_read_wifi(const char *wlan_iface,
if (!s->hostname[0])
strncpy(s->hostname, s->mac, sizeof(s->hostname)-1);
/* RSN = SNR estimado (signal - noise) */
/* Estimated SNR (signal - noise). */
s->rssi = s->signal - s->noise;
if (s->rssi < 0) s->rssi = 0;
/* CCQ: métrica 0-1000
* -50 dBm → 1000 (excelente)
* -90 dBm → 0 (muy malo)
* fórmula lineal: (signal + 90) * 25, limitado 0-1000 */
/* CCQ: 0-1000 quality metric
* -50 dBm → 1000 (excellent)
* -90 dBm → 0 (very poor)
* Linear mapping: (signal + 90) * 25, clamped to the range 0-1000.
*/
int ccq = (s->signal + 90) * 25;
s->ccq = (ccq < 0) ? 0 : (ccq > 1000) ? 1000 : ccq;
}
@@ -210,11 +213,11 @@ int clients_read_wifi(const char *wlan_iface,
}
/* ═══════════════════════════════════════════════════════════════════
Construir JSON sta_table para un VAP
Build the sta_table JSON array for a VAP.
═══════════════════════════════════════════════════════════════════
El JSON array resultante se anida dentro de vap_table[i].sta_table
en el payload inform. Ejemplo de entrada:
The resulting JSON array is embedded in vap_table[i].sta_table
within the inform payload. Example input:
{
"mac": "aa:bb:cc:dd:ee:ff",
"ip": "192.168.1.100",
+25 -21
View File
@@ -4,31 +4,35 @@
/*
* openuf - clients.h
*
* Enumera clientes conectados (WiFi y ethernet) para el sta_table
* del payload inform.
* Enumerates connected clients (Wi-Fi and Ethernet) for the
* sta_table in the inform payload.
*
* ── WiFi: iw dev <iface> station dump ───────────────────────────
* ── Wi-Fi: iw dev <iface> station dump ───────────────────────────
*
* Por cada cliente asociado devuelve:
* MAC, señal (dBm), tx/rx bitrate (MBit/s), tx/rx bytes,
* tx/rx packets, connected time (segundos)
* Returns the following information for each associated client:
* MAC address, signal strength (dBm), TX/RX bitrate (Mbit/s),
* TX/RX bytes, TX/RX packets, and connected time (seconds).
*
* ── IP del cliente: /proc/net/arp ───────────────────────────────
* ── Client IP Address: /proc/net/arp ─────────────────────────────
*
* Cruce MAC → IP. Solo entradas completas (flags=0x2).
* Maps MAC addresses to IP addresses. Only complete entries
* (flags = 0x2) are used.
*
* ── Hostname: /tmp/dhcp.leases (dnsmasq) ────────────────────────
* ── Hostname: /tmp/dhcp.leases (dnsmasq) ────────────────────────
*
* Formato: timestamp MAC IP hostname client-id
* Format: timestamp MAC IP hostname client-id
*
* ── Ethernet: bridge fdb show ───────────────────────────────────
*
* MACs dinámicas (no permanent, no multicast) en el bridge.
* Discovers dynamic MAC addresses (excluding permanent and
* multicast entries) in the bridge forwarding database.
*
* ── CCQ (Client Connection Quality) ─────────────────────────────
* ── CCQ (Client Connection Quality) ─────────────────────────────
*
* Estimated 01000 quality metric derived from RSSI. The
* UniFi Controller displays it as the client's signal quality
* indicator.
*
* Métrica 0-1000 basada en RSSI. El controlador la muestra
* como barra de calidad de señal del cliente.
* CCQ = clamp((signal + 90) * 25, 0, 1000)
*/
@@ -42,7 +46,7 @@ typedef struct {
char mac[32];
char ip[64];
char hostname[64];
int signal; /* RSSI dBm (negativo) */
int signal; /* RSSI dBm (negative) */
int noise; /* dBm */
int rssi; /* SNR ≈ signal - noise */
long tx_rate; /* kbps */
@@ -51,7 +55,7 @@ typedef struct {
long long rx_bytes;
long long tx_packets;
long long rx_packets;
int uptime; /* segundos conectado */
int uptime; /* seconds online */
char radio[8]; /* "ng" / "na" / "6g" */
int channel;
char vap_name[32];
@@ -60,15 +64,15 @@ typedef struct {
bool is_wired;
} sta_info_t;
/* Lee clientes WiFi de una interfaz. Devuelve nº de clientes. */
/* Reads Wi-Fi clients from an interface. Returns the number of clients. */
int clients_read_wifi(const char *wlan_iface,
const char *radio_band,
int channel,
sta_info_t *out,
int max_out);
/* Construye JSON array sta_table para un VAP.
* El caller debe liberar con json_object_put(). */
/* Creates a JSON array `sta_table` for a VAP.
* The caller must free it using `json_object_put()` */
struct json_object *clients_build_sta_table(const char *wlan_iface,
const char *radio_band,
int channel,
@@ -76,10 +80,10 @@ struct json_object *clients_build_sta_table(const char *wlan_iface,
int vlan_id,
int is_11r);
/* Busca IP en /proc/net/arp dado un MAC. */
/* Look up the IP address in /proc/net/arp given a MAC address. */
int clients_mac_to_ip(const char *mac, char *ip_out, size_t sz);
/* Busca hostname en /tmp/dhcp.leases dado un MAC. */
/* Find the hostname in /tmp/dhcp.leases given a MAC address */
int clients_mac_to_hostname(const char *mac, char *out, size_t sz);
#endif /* OPENUF_CLIENTS_H */
+89 -89
View File
@@ -1,50 +1,50 @@
/*
* openuf - inform.c
*
* Protocolo Inform de UniFi — implementación completa.
* UniFi Inform Protocol — full implementation.
*
* ── CÓMO FUNCIONA ────────────────────────────────────────────────────
* ── HOW IT WORKS ─────────────────────────────────────────────────────
*
* Cada 10 segundos el AP hace HTTP POST a http://<controller>:8080/inform
* con un paquete binario TNBU que contiene JSON cifrado con AES-128-CBC.
* Every 10 seconds the AP makes an HTTP POST to http://<controller>:8080/inform
* with a binary TNBU packet containing JSON encrypted with AES-128-CBC.
*
* El controlador responde con otro paquete TNBU. El AP descifra, parsea
* el JSON y ejecuta la acción (_type).
* The controller responds with another TNBU packet. The AP decrypts, parses
* the JSON, and executes the action (_type).
*
* ── PAQUETE BINARIO TNBU ─────────────────────────────────────────────
* ── TNBU BINARY PACKET ───────────────────────────────────────────────
*
* Offset Bytes Campo
* Offset Bytes Field
* ------ ----- -----
* 0 4 Magic "TNBU"
* 4 4 Versión paquete (=0), uint32 BE
* 8 6 MAC del AP
* 14 2 Flags: bit0=cifrado, bit1=zlib
* 16 16 IV de AES (cuando cifrado)
* 32 4 Versión de datos (=1), uint32 BE
* 36 4 Longitud del payload, uint32 BE
* 40 N Payload JSON, cifrado con AES-128-CBC
* 4 4 Packet version (=0), uint32 BE
* 8 6 AP MAC address
* 14 2 Flags: bit0=encrypted, bit1=zlib
* 16 16 AES IV (when encrypted)
* 32 4 Data version (=1), uint32 BE
* 36 4 Payload length, uint32 BE
* 40 N JSON payload, encrypted with AES-128-CBC
*
* ── CÓMO SE LEEN LOS PARÁMETROS ──────────────────────────────────────
* ── HOW PARAMETERS ARE READ ──────────────────────────────────────────
*
* CPU: sysinfo_cpu_percent() → /proc/stat (delta 2 llamadas)
* RAM: sysinfo_mem() → /proc/meminfo
* Interfaces: sysinfo_iface() → /proc/net/dev + /sys/class/net/
* Radios: sysinfo_radio() → iw dev <iface> info + survey
* VAPs UCI: wlan_get_vap_table() → libuci wireless.*
* Clientes WiFi: clients_build_sta_table() → iw dev <iface> station dump
* Clientes IP: clients_mac_to_ip() → /proc/net/arp
* Clientes nombre: clients_mac_to_hostname() → /tmp/dhcp.leases
* LLDP vecinos: lldp_read_neighbors() → lldpctl -f json
* CPU: sysinfo_cpu_percent() → /proc/stat (delta across 2 calls)
* RAM: sysinfo_mem() → /proc/meminfo
* Interfaces: sysinfo_iface() → /proc/net/dev + /sys/class/net/
* Radios: sysinfo_radio() → iw dev <iface> info + survey
* UCI VAPs: wlan_get_vap_table() → libuci wireless.*
* WiFi clients: clients_build_sta_table() → iw dev <iface> station dump
* IP clients: clients_mac_to_ip() → /proc/net/arp
* Client names: clients_mac_to_hostname() → /tmp/dhcp.leases
* LLDP neighbors: lldp_read_neighbors() → lldpctl -f json
*
* ── CICLO DE ADOPCIÓN ────────────────────────────────────────────────
* ── ADOPTION CYCLE ───────────────────────────────────────────────────
*
* 1. AP envía inform con key=DEFAULT, default=true, state=1
* 2. Controller responde: {_type:"cmd", cmd:"set-adopt",
* key:"nuevaclave32hex", uri:"http://..."}
* 3. AP guarda nueva clave + URL en state.json, adopted=true
* 4. AP envía inform con nueva clave, state=4, default=false
* 5. Controller responde: {_type:"setstate", radio_table:[...], vap_table:[...]}
* 6. AP aplica config WiFi via wlan_apply_config() → libuci → wifi reload
* 1. AP sends inform with key=DEFAULT, default=true, state=1
* 2. Controller responds: {_type:"cmd", cmd:"set-adopt",
* key:"new32hexkey", uri:"http://..."}
* 3. AP saves the new key + URL to state.json, adopted=true
* 4. AP sends inform with the new key, state=4, default=false
* 5. Controller responds: {_type:"setstate", radio_table:[...], vap_table:[...]}
* 6. AP applies WiFi config via wlan_apply_config() → libuci → wifi reload
*/
#include <stdio.h>
@@ -95,10 +95,10 @@ static int valid_authkey(const char *key)
}
/* ═══════════════════════════════════════════════════════════════════
sys_stats — CPU y memoria del sistema
sys_stats — CPU and memory of the system
═══════════════════════════════════════════════════════════════════
El controlador muestra CPU y RAM en la vista del dispositivo.
Leemos /proc/stat y /proc/meminfo directamente.
The controller shows CPU and RAM in the device view.
We read /proc/stat and /proc/meminfo directly.
*/
static struct json_object *build_sys_stats(void)
{
@@ -121,7 +121,7 @@ static struct json_object *build_sys_stats(void)
json_object_object_add(o, "mem_buffer", json_object_new_int(0));
}
/* CPU — delta respecto a llamada anterior (cada ~10s da buen promedio) */
/* CPU — delta relative to the previous call (every ~10s gives a good average) */
json_object_object_add(o, "cpu",
json_object_new_int(sysinfo_cpu_percent()));
@@ -129,11 +129,11 @@ static struct json_object *build_sys_stats(void)
}
/* ═══════════════════════════════════════════════════════════════════
if_table — estadísticas de interfaces de red
if_table — network interface statistics
═══════════════════════════════════════════════════════════════════
Reportamos todos los puertos ethernet del modelo.
Leemos /proc/net/dev para contadores y /sys/class/net/<iface>/
para velocidad, duplex y estado del enlace.
All Ethernet ports on the model are reported.
/proc/net/dev is read for counters, and /sys/class/net/<iface>/
for speed, duplex, and link status.
*/
static struct json_object *build_if_table(const uf_model_t *m,
const openuf_state_t *st)
@@ -184,10 +184,10 @@ static struct json_object *build_if_table(const uf_model_t *m,
}
/* ═══════════════════════════════════════════════════════════════════
radio_table — definición estática del hardware de radio
radio_table — static definition of the radio hardware
═══════════════════════════════════════════════════════════════════
Describe las capacidades físicas de cada radio al controlador.
El controlador usa esto para saber qué frecuencias y modos soporta.
Describes the physical capabilities of each radio to the controller.
The controller uses this to know which frequencies and modes it supports.
*/
static void build_radio_table(struct json_object *root,
const uf_model_t *m)
@@ -215,12 +215,12 @@ static void build_radio_table(struct json_object *root,
}
/* ═══════════════════════════════════════════════════════════════════
radio_table_stats — estadísticas dinámicas de canal
radio_table_stats — dynamic channel statistics
═══════════════════════════════════════════════════════════════════
Leemos en tiempo real la utilización del canal con:
iw dev wlan0 survey dump → active/busy/tx/rx time
iw dev wlan0 info → canal actual, potencia
El controlador muestra estos datos en la vista de RF.
Channel utilization is read in real time using:
iw dev wlan0 survey dump → active/busy/tx/rx time
iw dev wlan0 info → current channel, power
The controller displays this data in the RF view.
*/
static struct json_object *build_radio_table_stats(const uf_model_t *m)
{
@@ -229,13 +229,13 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
for (int i = 0; i < m->radio_map_len; i++) {
const uf_radio_map_t *rm = &m->radio_map[i];
/* Mapear "radio0" → "wlan0" por convención OpenWrt */
/* Map "radio0" → "wlan0" by OpenWrt convention */
char wlan_iface[32];
int ridx = 0;
sscanf(rm->device, "radio%d", &ridx);
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%d", ridx);
/* Nombre del radio en la tabla estática */
/* Radio name in the static table */
const char *radio_name = (i < m->radio_table_len)
? m->radio_table[i].name : wlan_iface;
int default_ch = (i < m->radio_table_len)
@@ -272,10 +272,10 @@ static struct json_object *build_radio_table_stats(const uf_model_t *m)
}
/* ═══════════════════════════════════════════════════════════════════
port_table — estado real de los puertos ethernet
port_table — Real/actual status of the ethernet ports
═══════════════════════════════════════════════════════════════════
Leemos /sys/class/net/<iface>/speed y operstate para
reflejar el estado real de cada puerto en el controlador.
/sys/class/net/<iface>/speed and operstate are read to
reflect the actual status of each port on the controller.
*/
static void build_port_table(struct json_object *root,
const uf_model_t *m)
@@ -328,24 +328,24 @@ static void build_eth_table(struct json_object *root, const uf_model_t *m)
}
/* ═══════════════════════════════════════════════════════════════════
vap_table — VAPs activas con clientes conectados (sta_table)
vap_table — active VAPs with connected clients (sta_table)
═══════════════════════════════════════════════════════════════════
Para cada VAP activa en UCI:
1. Leemos estasticas de la interfaz wlan con sysinfo_iface()
2. Obtenemos el canal actual con sysinfo_radio()
3. Enumeramos clientes con clients_build_sta_table()
→ iw dev wlan0 station dump (sal, bitrate, bytes, uptime)
For each active VAP in UCI:
1. Interface statistics for the wlan are read with sysinfo_iface()
2. The current channel is obtained with sysinfo_radio()
3. Clients are enumerated with clients_build_sta_table()
→ iw dev wlan0 station dump (signal, bitrate, bytes, uptime)
→ /proc/net/arp (MAC → IP)
→ /tmp/dhcp.leases (MAC → hostname)
El sta_table anidado es lo que el controlador usa para:
- Mostrar clientes en el dashboard
- Calcular estadísticas por cliente
- Dibujar la topología de la red
The nested sta_table is what the controller uses to:
- Display clients on the dashboard
- Calculate per-client statistics
- Draw the network topology
*/
static struct json_object *build_vap_table(const uf_model_t *m)
{
/* Obtener lista de VAPs desde UCI */
/* Get list of VAPs from UCI */
struct json_object *uci_vaps = wlan_get_vap_table(m);
int nvaps = json_object_array_length(uci_vaps);
@@ -374,7 +374,7 @@ static struct json_object *build_vap_table(const uf_model_t *m)
if (json_object_object_get_ex(vap, "fast_roaming_enabled", &v))
is_11r = json_object_get_boolean(v);
/* Mapear banda → interfaz wlan y canal actual */
/* Map band → wlan interface and current channel */
char wlan_iface[32] = "phy0-ap0";
if (ifname && ifname[0])
snprintf(wlan_iface, sizeof(wlan_iface), "%s", ifname);
@@ -394,17 +394,17 @@ static struct json_object *build_vap_table(const uf_model_t *m)
}
}
/* Estadísticas de la interfaz inalámbrica */
/* Wireless interface statistics */
iface_stats_t iface_st;
sysinfo_iface(wlan_iface, &iface_st);
/* Clientes conectados a esta VAP */
/* Clients connected to this VAP */
struct json_object *sta_tbl =
clients_build_sta_table(wlan_iface, radio, channel, vap_name,
vlan_id, is_11r);
int num_sta = json_object_array_length(sta_tbl);
/* Calcular tx_power del radio correspondiente */
/* Calculate tx_power of the corresponding radio */
int tx_pwr = 20;
radio_stats_t rs2;
if (sysinfo_radio(wlan_iface, &rs2) == 0 && rs2.tx_power)
@@ -452,7 +452,7 @@ static struct json_object *build_vap_table(const uf_model_t *m)
json_object_new_string("user"));
json_object_object_add(o, "ccq",
json_object_new_int(0));
/* sta_table anidado — clientes de ESTA VAP */
/* Nested sta_table — clients of THIS VAP */
json_object_object_add(o, "sta_table", sta_tbl);
json_object_array_add(arr, o);
@@ -481,13 +481,13 @@ static struct json_object *collect_sta_table(struct json_object *vap_table)
}
/* ═══════════════════════════════════════════════════════════════════
build_payload — ensamblado completo del JSON inform
build_payload — Complete assembly of the inform JSON
═══════════════════════════════════════════════════════════════════ */
static char *build_payload(const openuf_state_t *st,
const uf_model_t *m,
long uptime)
{
/* MAC sin colones → serial (uppercase) */
/* MAC without colons → serial (uppercase) */
char mac_clean[32] = {0};
{
const char *s = st->mac; int j = 0;
@@ -511,7 +511,7 @@ static char *build_payload(const openuf_state_t *st,
struct json_object *root = json_object_new_object();
/* ── Identidad del dispositivo ──────────────────────────────── */
/* ── Device identity ──────────────────────────────── */
json_object_object_add(root, "mac",
json_object_new_string(st->mac));
json_object_object_add(root, "serial",
@@ -564,17 +564,17 @@ static char *build_payload(const openuf_state_t *st,
/* ── CPU + RAM ──────────────────────────────────────────────── */
json_object_object_add(root, "sys_stats", build_sys_stats());
/* ── Interfaces ethernet con contadores reales ──────────────── */
/* ── Ethernet interfaces with real counters ──────────────── */
json_object_object_add(root, "if_table", build_if_table(m, st));
/* ── Capacidades de radio (estático del modelo) ─────────────── */
/* ── Radio capabilities (static, from the model) ─────────────── */
build_radio_table(root, m);
/* ── Utilización de canal en tiempo real ────────────────────── */
/* ── Real-time channel utilization ────────────────────── */
json_object_object_add(root, "radio_table_stats",
build_radio_table_stats(m));
/* ── Puertos ethernet con estado real ───────────────────────── */
/* ── Ethernet ports with actual status ───────────────────────── */
build_port_table(root, m);
build_eth_table(root, m);
@@ -585,10 +585,10 @@ static char *build_payload(const openuf_state_t *st,
json_object_object_add(root, "vap_table", vap_table);
json_object_object_add(root, "sta_table", sta_table);
/* ── Vecinos LLDP para topología visual ─────────────────────── */
/* ── LLDP neighbors for visual topology ─────────────────────── */
json_object_object_add(root, "lldp_table", lldp_read_neighbors());
/* Contadores globales */
/* Global counters */
json_object_object_add(root, "bytes_r", json_object_new_int(0));
json_object_object_add(root, "bytes_d", json_object_new_int(0));
json_object_object_add(root, "num_sta", json_object_new_int(station_count));
@@ -610,7 +610,7 @@ static char *build_payload(const openuf_state_t *st,
}
/* ═══════════════════════════════════════════════════════════════════
Paquete binario TNBU
TNBU binary packet
═══════════════════════════════════════════════════════════════════ */
static unsigned char *build_packet(const char *mac_hex,
const char *key_hex,
@@ -668,7 +668,7 @@ static unsigned char *build_packet(const char *mac_hex,
}
/* ═══════════════════════════════════════════════════════════════════
Parsear respuesta binaria del controlador
Parse binary response from the controller
═══════════════════════════════════════════════════════════════════ */
static char *parse_packet(const unsigned char *data, size_t data_len,
const char *key_hex)
@@ -717,13 +717,13 @@ static char *parse_packet(const unsigned char *data, size_t data_len,
}
/* ═══════════════════════════════════════════════════════════════════
Procesar comando JSON del controlador
Process JSON command from the controller
═══════════════════════════════════════════════════════════════════
_type == "noop" → no hacer nada
_type == "noop" → do nothing
_type == "cmd" → set-adopt / reboot / reset / locate
_type == "setstate" → aplicar radio_table + vap_table via UCI
_type == "setparam" → cambiar un parámetro individual
_type == "setstate" → apply radio_table + vap_table via UCI
_type == "setparam" → change a single parameter
*/
static void handle_response(openuf_state_t *st,
const uf_model_t *model,
@@ -896,7 +896,7 @@ static void handle_response(openuf_state_t *st,
system("reboot &");
} else if (!strcmp(cmd, "locate")) {
/* Parpadear LED — en OpenWrt: echo 1 > /sys/class/leds/.../trigger */
/* Blink LED — on OpenWrt: echo 1 > /sys/class/leds/.../trigger */
strcpy(action_out, "locate");
} else {
snprintf(action_out, 64, "cmd:%s", cmd);
@@ -904,7 +904,7 @@ static void handle_response(openuf_state_t *st,
return;
}
/* ── setstate — configuración WiFi del controlador ──────────── */
/* ── setstate — WiFi configuration from the controller ──────────── */
if (!strcmp(type, "setstate")) {
if (json_object_object_get_ex(resp, "cfgversion", &v))
snprintf(st->cfgversion, sizeof(st->cfgversion),
@@ -937,7 +937,7 @@ static void handle_response(openuf_state_t *st,
}
/* ═══════════════════════════════════════════════════════════════════
inform_send — función principal pública
inform_send — main public function
═══════════════════════════════════════════════════════════════════ */
int inform_send(openuf_state_t *st,
const uf_model_t *model,
@@ -961,7 +961,7 @@ int inform_send(openuf_state_t *st,
LOG("Sending inform: adopted=%d, authkey=%.8s..., inform_url=%s",
st->adopted, key_hex, st->inform_url);
/* MAC sin colones */
/* MAC without colons */
char mac_hex[32] = {0};
{
const char *s = st->mac; int j = 0;
+28 -29
View File
@@ -1,33 +1,33 @@
/*
* openuf - lldp.c
*
* LLDP completo: envío de frames propios + lectura de vecinos.
* Complete LLDP: sending of own frames + reading of neighbors.
*
* ── Construcción del frame ────────────────────────────────────────
* ── Frame construction ─────────────────────────────────────────────
*
* Los TLVs LLDP tienen cabecera de 2 bytes:
* bit 15..9 → tipo (7 bits)
* bit 8..0 → longitud (9 bits, max 511 bytes)
* LLDP TLVs have a 2-byte header:
* bit 15..9 → type (7 bits)
* bit 8..0 → length (9 bits, max 511 bytes)
*
* uint16_t header_be = (type << 9) | (len & 0x1ff)
*
* Ejemplo: Chassis ID TLV (type=1), 7 bytes de valor:
* Example: Chassis ID TLV (type=1), 7 bytes of value:
* header = (1 << 9) | 7 = 0x0207
* → bytes: 0x02 0x07 [subtype=4] [MAC 6 bytes]
*
* ── Envío con AF_PACKET ───────────────────────────────────────────
* ── Sending with AF_PACKET ────────────────────────────────────────
*
* 1. socket(AF_PACKET, SOCK_RAW, htons(0x88cc))
* 2. ioctl(SIOCGIFINDEX) → ifindex
* 3. Construir frame completo en buffer
* 4. sendto() con sockaddr_ll
* 3. Build the complete frame in a buffer
* 4. sendto() with sockaddr_ll
*
* Sin CAP_NET_RAW (no root) → socket() devuelve EPERM.
* Lo ignoramos silenciosamente (LLDP es opcional).
* Without CAP_NET_RAW (not root) → socket() returns EPERM.
* This is silently ignored (LLDP is optional).
*
* ── Lectura de vecinos con lldpctl ───────────────────────────────
* ── Reading neighbors with lldpctl ───────────────────────────────
*
* lldpctl -f json retorna:
* lldpctl -f json returns:
* {
* "lldp": {
* "interface": [
@@ -66,12 +66,12 @@
#include "lldp.h"
/* ─── Constantes ────────────────────────────────────────────────── */
/* ─── Constants ────────────────────────────────────────────────── */
static const uint8_t LLDP_DST[6] = {0x01,0x80,0xc2,0x00,0x00,0x0e};
#define LLDP_ETHERTYPE 0x88cc
#define CAP_WLAN_AP 0x0040
/* ─── Escribir TLV en buffer ────────────────────────────────────── */
/* ─── Write TLV to buffer ────────────────────────────────────── */
static int tlv_write(uint8_t *buf, int pos, int maxlen,
int type, const uint8_t *val, int vlen)
{
@@ -90,7 +90,7 @@ static int tlv_str(uint8_t *buf, int pos, int maxlen,
(const uint8_t*)str, (int)strlen(str));
}
/* ─── Parsear MAC "aa:bb:cc:dd:ee:ff" → bytes ──────────────────── */
/* ─── Parse MAC "aa:bb:cc:dd:ee:ff" → bytes ──────────────────── */
static void parse_mac(const char *s, uint8_t out[6])
{
unsigned int b[6]={0};
@@ -109,7 +109,7 @@ int lldp_send_frame(const char *ifname,
{
/* Socket raw — requiere root */
int fd = socket(AF_PACKET, SOCK_RAW, htons(LLDP_ETHERTYPE));
if (fd < 0) return -1; /* EPERM sin root → silencioso */
if (fd < 0) return -1; /* EPERM without root → silent */
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
@@ -162,9 +162,9 @@ int lldp_send_frame(const char *ifname,
0x00, (uint8_t)(CAP_WLAN_AP >> 8),
0x00, (uint8_t)(CAP_WLAN_AP & 0xff)
};
/* Corregir: CAP_WLAN_AP = 0x0040, un solo byte basta */
/* Fix: CAP_WLAN_AP = 0x0040, a single byte is enough */
v[1] = 0x00; v[0] = 0x00;
/* bit 6 de los 16 bits de capabilities */
/* bit 6 of the 16 capability bits */
uint16_t cap = CAP_WLAN_AP;
v[0] = (cap >> 8) & 0xff; v[1] = cap & 0xff;
v[2] = v[0]; v[3] = v[1]; /* enabled = same */
@@ -197,16 +197,15 @@ bool lldp_available(void)
}
/* ═══════════════════════════════════════════════════════════════════
lldp_read_neighbors — parsea JSON de lldpctl
lldp_read_neighbors — parses JSON from lldpctl
═══════════════════════════════════════════════════════════════════
Navega: root → "lldp" → "interface" (array) → cada vecino.
Por cada vecino extrae: chassis.id, chassis.name, chassis.descr,
port.id, port.descr, y el nombre de la interfaz local.
El resultado se incluye en lldp_table[] del payload inform.
El controlador lo usa para dibujar las líneas de conexión en
la topología visual (qué switch/puerto conecta a este AP).
Navigate: root → "lldp" → "interface" (array) → each neighbor.
For each neighbor, extract: chassis.id, chassis.name, chassis.descr,
port.id, port.descr, and the name of the local interface.
The result is included in lldp_table[] of the inform payload.
The controller uses it to draw the connection lines in
the visual topology (which switch/port this AP connects to).
*/
struct json_object *lldp_read_neighbors(void)
{
@@ -230,7 +229,7 @@ struct json_object *lldp_read_neighbors(void)
struct json_object *root = json_tokener_parse(buf);
if (!root) return result;
/* Navegar: root.lldp.interface[] */
/* Browse: root.lldp.interface[] */
struct json_object *lldp_o, *iface_arr;
if (!json_object_object_get_ex(root, "lldp", &lldp_o)) goto done;
if (!json_object_object_get_ex(lldp_o, "interface", &iface_arr)) goto done;
@@ -241,7 +240,7 @@ struct json_object *lldp_read_neighbors(void)
struct json_object *iface = json_object_array_get_idx(iface_arr, i);
if (!iface) continue;
/* Puerto local */
/* Local port */
struct json_object *tmp_o;
const char *local_port = "";
if (json_object_object_get_ex(iface, "name", &tmp_o))
+20 -20
View File
@@ -6,18 +6,18 @@
*
* LLDP (Link Layer Discovery Protocol — IEEE 802.1AB)
*
* ── ENVÍO de frames LLDP propios ────────────────────────────────
* ── SENDING our own LLDP frames ────────────────────────────────
*
* El AP transmite frames LLDP por cada puerto ethernet.
* Esto permite al switch upstream registrar al AP como vecino,
* y al controlador UniFi construir el mapa de topología visual.
* The AP transmits LLDP frames on each Ethernet port.
* This allows the upstream switch to register the AP as a neighbor,
* and lets the UniFi controller build the visual topology map.
*
* Frame Ethernet:
* dst = 01:80:c2:00:00:0e (multicast LLDP estándar)
* dst = 01:80:c2:00:00:0e (standard multicast LLDP)
* src = MAC del AP
* type = 0x88cc
*
* Payload (TLVs encadenados):
* Payload (TLVs chained):
* Header TLV = [type:7bits | len_hi:1bit][len_lo:8bits]
*
* TLV type=1 Chassis ID subtype=4(MAC), value=MAC[6]
@@ -28,12 +28,12 @@
* TLV type=7 Capabilities cap=0x0040(WLAN-AP), en=0x0040
* TLV type=0 End of LLDPDU len=0
*
* ── LECTURA de vecinos: lldpctl -f json ─────────────────────────
* ── READING of neighbors: lldpctl -f json ─────────────────────────
*
* Si lldpd está instalado, leemos los vecinos detectados
* y los incluimos en lldp_table del payload inform.
* If lldpd is installed, the detected neighbors are read
* and included in lldp_table of the inform payload.
*
* lldp_table en el JSON inform:
* lldp_table in the inform JSON:
* [{
* "local_port": "eth0",
* "chassis_id": "aa:bb:cc:...",
@@ -43,30 +43,30 @@
* "port_desc": "to-AP"
* }]
*
* ── SIN lldpd ───────────────────────────────────────────────────
* ── Without lldpd ───────────────────────────────────────────────────
*
* lldp_send_frame() funciona sin lldpd (usa raw socket directo).
* lldp_read_neighbors() retorna array vacío si no hay lldpctl.
* lldp_send_frame() works without lldpd (it uses a direct raw socket).
* lldp_read_neighbors() returns an empty array if lldpctl is not present.
*/
#include <stdbool.h>
#include <json-c/json.h>
/* Envía un frame LLDP por raw socket AF_PACKET.
* Requiere ejecutar como root (CAP_NET_RAW).
* Devuelve 0 si ok, -1 si error (sin root → error silencioso). */
/* Sends an LLDP frame via raw AF_PACKET socket.
* Requires running as root (CAP_NET_RAW).
* Returns 0 on success, -1 on error (without root → silent error). */
int lldp_send_frame(const char *ifname,
const char *mac_str,
const char *hostname,
const char *model_desc,
int ttl);
/* Lee vecinos LLDP de lldpctl y retorna JSON array lldp_table.
* Si lldpctl no está, retorna array vacío (no falla).
* Caller libera con json_object_put(). */
/* Reads LLDP neighbors from lldpctl and returns a JSON array lldp_table.
* If lldpctl is not present, returns an empty array (does not fail).
* Caller frees it with json_object_put(). */
struct json_object *lldp_read_neighbors(void);
/* true si lldpctl está instalado */
/* true if lldpctl is installed */
bool lldp_available(void);
#endif /* OPENUF_LLDP_H */
+9 -9
View File
@@ -1,10 +1,10 @@
/*
* openuf - main.c
*
* Daemon principal. Bucle con tres tareas:
* 1. Announce UDP broadcast+multicast cada 10s (descubrimiento L2)
* 2. Inform HTTP POST cifrado cada 10s (adopción + telemetría)
* 3. LLDP Raw frame L2 cada 30s (topología visual en UniFi)
* Main daemon. Loop with three tasks:
* 1. Announce UDP broadcast+multicast each 10s (discovery L2)
* 2. Inform HTTP POST cifrado each 10s (adoption + telemetrics)
* 3. LLDP Raw frame L2 each 30s (visual topology in UniFi)
*/
#include <stdio.h>
@@ -127,13 +127,13 @@ int main(int argc, char *argv[])
}
}
/* ── Descripción LLDP del dispositivo ───────────────────────── */
/* ── LLDP device description ───────────────────────── */
char lldp_desc[128];
snprintf(lldp_desc, sizeof(lldp_desc),
"%s %s%s (openuf)",
model->model_display, model->fw_pre, model->fw_ver);
/* ── Bucle principal ─────────────────────────────────────────── */
/* ── Main loop ─────────────────────────────────────────── */
time_t start_time = time(NULL);
time_t last_announce = 0;
time_t last_inform = 0;
@@ -154,12 +154,12 @@ int main(int argc, char *argv[])
last_announce = now;
}
/* LLDP frames por cada interfaz ethernet */
/* LLDP frames for each Ethernet interface */
if ((now - last_lldp) >= LLDP_INTERVAL) {
LOG("Sending LLDP frames");
for (int i = 0; i < model->port_table_len; i++) {
const char *iface = model->port_table[i].ifname;
/* Leer MAC real de la interfaz si disponible */
/* Read the actual MAC of the interface if available */
char iface_mac[32];
if (get_mac(iface, iface_mac, sizeof(iface_mac)) != 0)
strncpy(iface_mac, mac_str, sizeof(iface_mac)-1);
@@ -178,7 +178,7 @@ int main(int argc, char *argv[])
last_inform = now;
LOG("Sending inform");
/* Actualizar IP en cada ciclo */
/* Update IP each cycle */
char new_ip[64] = {0};
if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 ||
get_ip("eth0", new_ip, sizeof(new_ip)) == 0)
+1 -1
View File
@@ -166,7 +166,7 @@ const uf_model_t model_uapg2aclr = {
.radio_map=uapg2aclr_rmap, .radio_map_len=2,
};
/* ─── Registro de modelos ─────────────────────────────────────── */
/* ─── Model Registry ─────────────────────────────────────── */
static const uf_model_t *all_models[] = {
&model_u6inwall,
&model_u6lite,
+23 -23
View File
@@ -1,26 +1,26 @@
/*
* openuf - sysinfo.c
*
* Lee estasticas del sistema para el payload inform.
* Reads system statistics for the inform payload.
*
* ── CPU: /proc/stat ──────────────────────────────────────────────────
*
* Formato: cpu user nice system idle iowait irq softirq steal
* Format: cpu user nice system idle iowait irq softirq steal
*
* El uso se calcula con dos snapshots separados en el tiempo:
* activo = user + nice + system + irq + softirq + steal
* total = activo + idle + iowait
* uso % = (Δactivo / Δtotal) × 100
* Usage is calculated with two snapshots taken at different times:
* active = user + nice + system + irq + softirq + steal
* total = active + idle + iowait
* usage % = (Δactive / Δtotal) × 100
*
* ── Memoria: /proc/meminfo ───────────────────────────────────────────
* ── Memory: /proc/meminfo ───────────────────────────────────────────
*
* MemTotal, MemFree, Buffers, Cached
* used = total - free - buffers - cached
*
* ── Interfaces: /proc/net/dev + /sys/class/net/<iface>/ ─────────────
*
* /proc/net/dev → contadores acumulados rx/tx
* /sys/class/net/speed → velocidad negociada (Mbps)
* /proc/net/dev → cumulative rx/tx counters
* /sys/class/net/speed → negotiated speed (Mbps)
* /sys/class/net/duplex → "full" / "half"
* /sys/class/net/operstate → "up" / "down" / "unknown"
* /sys/class/net/address → MAC
@@ -28,8 +28,8 @@
*
* ── Radio: iw dev <iface> info + survey dump ─────────────────────────
*
* info: canal actual, potencia TX
* survey dump: active/busy/tx/rx time → calcular % utilización
* info: current channel, TX power
* survey dump: active/busy/tx/rx time → calculate % utilization
*/
#define _GNU_SOURCE
@@ -47,7 +47,7 @@
#include "sysinfo.h"
/* ═══════════════════════════════════════════════════════════════════
Memoria
Memory
═══════════════════════════════════════════════════════════════════ */
int sysinfo_mem(mem_stats_t *out)
{
@@ -109,7 +109,7 @@ int sysinfo_cpu_percent(void)
}
/* ═══════════════════════════════════════════════════════════════════
Interfaz de red
Network interface
═══════════════════════════════════════════════════════════════════ */
static int read_sysfs_str(const char *iface, const char *file,
char *out, size_t sz)
@@ -167,7 +167,7 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
/* IP */
read_ip_ioctl(ifname, out->ip, sizeof(out->ip));
/* Contadores de /proc/net/dev */
/* Counters of /proc/net/dev */
FILE *f = fopen("/proc/net/dev", "r");
if (!f) return 0;
@@ -179,7 +179,7 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
char *colon = strchr(line, ':');
if (!colon) continue;
/* Extraer nombre de interfaz (puede tener espacios al inicio) */
/* Extract interface name (may have leading spaces) */
size_t end = colon - line;
while (end > 0 && line[end-1] == ' ') end--;
size_t start = 0;
@@ -210,24 +210,24 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
}
/* ═══════════════════════════════════════════════════════════════════
Radio WiFi
WiFi Radio
═══════════════════════════════════════════════════════════════════
1. iw dev wlan0 info → canal y potencia
Ejemplo:
1. iw dev wlan0 info → channel and power
Example:
Interface wlan0
channel 6 (2437 MHz), width: 20 MHz
txpower 20.00 dBm
2. iw dev wlan0 survey dump → utilización del canal
Buscamos el bloque con "[in use]":
2. iw dev wlan0 survey dump → channel utilization
We look for the block with "[in use]":
frequency: 2437 MHz [in use]
channel active time: 12345 ms
channel busy time: 987 ms
channel transmit time: 456 ms
channel receive time: 321 ms
Calculamos:
We calculate:
cu_total = busy/active × 100
cu_self_tx = transmit/active × 100
cu_self_rx = receive/active × 100
@@ -268,7 +268,7 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
in_use = 1; active=busy=tx_t=rx_t=0; continue;
}
if (!in_use) continue;
/* Nueva frecuencia sin [in use] resetea el bloque */
/* New frequency without [in use] resets the block */
if (strstr(line, "frequency:") && !strstr(line, "[in use]")) {
in_use = 0; continue;
}
@@ -287,7 +287,7 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
out->cu_self_rx = (int)(rx_t * 100 / active);
}
/* Número de clientes asociados */
/* Number of associated clients */
snprintf(cmd, sizeof(cmd),
"iw dev %s station dump 2>/dev/null | grep -c '^Station'",
iface);
+14 -14
View File
@@ -4,15 +4,15 @@
/*
* openuf - sysinfo.h
*
* Lee estasticas del sistema (CPU, RAM, interfaces, radios).
* Todas las lecturas son del kernel Linux directamente:
* Reads system statistics (CPU, RAM, interfaces, radios).
* All readings come directly from the Linux kernel:
*
* /proc/stat → uso CPU (deltas entre dos snapshots)
* /proc/meminfo → memoria total/libre/buffer/cache
* /proc/net/dev → contadores rx/tx por interfaz
* /proc/stat → CPU usage (deltas between two snapshots)
* /proc/meminfo → total/free/buffer/cache memory
* /proc/net/dev → rx/tx counters per interface
* /sys/class/net/ → speed, duplex, operstate, MAC
* iw dev <if> info → canal actual, potencia TX
* iw dev <if> survey dump → utilización del canal
* iw dev <if> info → current channel, TX power
* iw dev <if> survey dump → channel utilization
*/
#include <stdbool.h>
@@ -28,9 +28,9 @@ typedef struct {
int sysinfo_mem(mem_stats_t *out);
/* ── CPU ─────────────────────────────────────────────────────────── */
/* Retorna % uso CPU (0-100). Primera llamada retorna 0 (toma snapshot).
* Las siguientes calculan el delta respecto a la anterior.
* Con intervalo de 10s da un buen promedio de uso. */
/* Returns % CPU usage (0-100). The first call returns 0 (takes a snapshot).
* Subsequent calls compute the delta relative to the previous one.
* With a 10s interval this gives a good average of usage. */
int sysinfo_cpu_percent(void);
/* ── Interfaz de red ─────────────────────────────────────────────── */
@@ -39,7 +39,7 @@ typedef struct {
char mac[32];
char ip[64];
bool up;
int speed; /* Mbps: 10/100/1000; -1 si no disponible */
int speed; /* Mbps: 10/100/1000; -1 if not available */
bool full_duplex;
long long rx_bytes;
long long tx_bytes;
@@ -60,9 +60,9 @@ typedef struct {
char iface[32];
int channel;
int tx_power;
int cu_total; /* % uso canal total */
int cu_self_tx; /* % tiempo transmitiendo */
int cu_self_rx; /* % tiempo recibiendo */
int cu_total; /* % total channel usage */
int cu_self_tx; /* % time spent transmitting */
int cu_self_rx; /* % time spent receiving */
int num_sta;
int noise; /* dBm */
} radio_stats_t;
+57 -56
View File
@@ -373,7 +373,7 @@ void wlan_clear(void)
return;
}
/* Recopilar secciones a eliminar (no modificar durante iteración) */
/* Collect sections to remove (do not modify during iteration) */
char *to_del[64];
int ndel = 0;
struct uci_element *e;
@@ -403,14 +403,14 @@ void wlan_clear(void)
}
/* ═══════════════════════════════════════════════════════════════════
wlan_apply_radio — aplicar config de radio (canal, HT, potencia)
wlan_apply_radio — apply radio config (channel, HT, power)
═══════════════════════════════════════════════════════════════════
Lectura de parámetros del JSON del controlador:
Reading parameters from the controller's JSON:
channel → wireless.<device>.channel
ht → wireless.<device>.htmode ("HT20" / "HT40" / "HT80" / "HE80")
tx_power → wireless.<device>.txpower
min_rssi → no se mapea a UCI (requiere daemon externo)
min_rssi → not mapped to UCI (requires an external daemon)
*/
void wlan_apply_radio(struct json_object *radio_json,
const char *device_name)
@@ -452,7 +452,7 @@ void wlan_apply_radio(struct json_object *radio_json,
RP("ht", "htmode");
/* Canal: 0 = auto en UniFi */
/* Channel: 0 = auto in UniFi */
if (json_object_object_get_ex(radio_json, "channel", &v)) {
int ch = json_object_get_int(v);
if (ch == 0) {
@@ -474,7 +474,7 @@ void wlan_apply_radio(struct json_object *radio_json,
uci_set(ctx, &ptr);
}
/* Habilitar el radio */
/* Enable the radio */
snprintf(path, sizeof(path), "wireless.%s.disabled=0", device_name);
struct uci_ptr ptr;
if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK)
@@ -488,22 +488,22 @@ void wlan_apply_radio(struct json_object *radio_json,
}
/* ═══════════════════════════════════════════════════════════════════
Crear una VAP (wifi-iface UCI) desde un JSON VAP del controlador
Create a VAP (wifi-iface UCI) from a controller VAP JSON
═══════════════════════════════════════════════════════════════════
Parámetros del controlador que leemos y cómo los mapeamos:
Controller parameters we read and how we map them:
essid → wireless.openuf_X.ssid
x_passphrase → wireless.openuf_X.key
security → wireless.openuf_X.encryption (via sec_to_uci)
hide_ssid → wireless.openuf_X.hidden
guest_policy → wireless.openuf_X.isolate (aislamiento de clientes)
fast_roaming_enabled → ieee80211r, ft_over_ds, mobility_domain, ft_psk_generate_local
band_steering → ieee80211k, ieee80211v, rrm_neighbor_report, bss_transition
pmf_mode → ieee80211w (0/1/2)
wpa3_support → añadir "sae-mixed" si WPA2+WPA3
uapsd → uapsd (U-APSD power saving)
vlan_id → wireless.openuf_X.vlan_id (si ≠ 0)
x_passphrase → wireless.openuf_X.key
security → wireless.openuf_X.encryption (via sec_to_uci)
hide_ssid → wireless.openuf_X.hidden
guest_policy → wireless.openuf_X.isolate (client isolation)
fast_roaming_enabled → ieee80211r, ft_over_ds, mobility_domain, ft_psk_generate_local
band_steering → ieee80211k, ieee80211v, rrm_neighbor_report, bss_transition
pmf_mode → ieee80211w (0/1/2)
wpa3_support → add "sae-mixed" if WPA2+WPA3
uapsd → uapsd (U-APSD power saving)
vlan_id → wireless.openuf_X.vlan_id (if ≠ 0)
*/
static int apply_vap(struct uci_context *ctx,
struct uci_package *pkg,
@@ -535,7 +535,7 @@ static int apply_vap(struct uci_context *ctx,
snprintf(target_network, sizeof(target_network), "vlan%d", vid);
}
/* Nombre de sección: openuf_<idx>_<ssid_safe> */
/* Section name: openuf_<idx>_<ssid_safe> */
char safe[16] = {0};
safe_section_name(essid, safe, sizeof(safe));
char sec_name[48];
@@ -556,7 +556,7 @@ static int apply_vap(struct uci_context *ctx,
* Preserve the controller's WLAN configuration ID. Inform telemetry must
* refer to this ObjectId; a label such as "user" is not a valid VAP ID.
* Controller versions use different keys, so accept the known variants.
*/
*/
const char *vap_id = NULL;
const char *id_keys[] = { "_id", "id", "wlanconf_id" };
for (size_t i = 0; i < sizeof(id_keys) / sizeof(id_keys[0]); i++) {
@@ -571,23 +571,23 @@ static int apply_vap(struct uci_context *ctx,
if (vap_id)
UCI_SET(ctx, "wireless", sec_name, "openuf_vap_id", vap_id);
/* Contraseña */
/* Password */
if (pass && pass[0] && strcmp(security,"open") != 0)
UCI_SET(ctx, "wireless", sec_name, "key", pass);
/* SSID oculto */
/* hidden SSID */
int hidden = 0;
if (json_object_object_get_ex(vap_json, "hide_ssid", &v))
hidden = json_object_get_boolean(v) ? 1 : 0;
UCI_SET_INT(ctx, "wireless", sec_name, "hidden", hidden);
/* Aislamiento de clientes (guest network) */
/* Client isolation (guest network) */
int isolate = 0;
if (json_object_object_get_ex(vap_json, "guest_policy", &v))
isolate = json_object_get_boolean(v) ? 1 : 0;
UCI_SET_INT(ctx, "wireless", sec_name, "isolate", isolate);
/* U-APSD (ahorro de energía para clientes móviles) */
/* U-APSD (power saving for mobile clients) */
int uapsd = 1;
if (json_object_object_get_ex(vap_json, "uapsd", &v))
uapsd = json_object_get_boolean(v) ? 1 : 0;
@@ -595,22 +595,22 @@ static int apply_vap(struct uci_context *ctx,
/* ── PMF (Protected Management Frames / 802.11w) ──────────────
* "disabled" → 0, "optional" → 1, "required" → 2
* WPA3 (sae/sae-mixed) siempre requiere ieee80211w=2 */
* WPA3 (sae/sae-mixed) always requires "optional" or " required" ieee80211w=2 */
int pmf = 0;
if (json_object_object_get_ex(vap_json, "pmf_mode", &v)) {
const char *pm = json_object_get_string(v);
if (!strcmp(pm, "optional")) pmf = 1;
if (!strcmp(pm, "required")) pmf = 2;
}
/* WPA3 obliga PMF=2 */
/* WPA3 forces PMF=2 */
if (!strcmp(security,"wpa3") || !strcmp(security,"wpa3transition") ||
!strcmp(security,"wpa3enterprise"))
pmf = 2;
UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211w", pmf);
/* ── Fast Roaming (802.11r FT) ────────────────────────────────
* Permite que los clientes se muevan entre APs sin re-autenticación
* completa. El handshake FT sólo tarda ~50ms vs ~200-300ms normal. */
* Allows clients to move between APs without re-authentication
* complete. The FT handshake only takes ~50ms vs ~200-300ms for a normal one. */
const char *ft_keys[] = {
"fast_roaming_enabled", "fast_roaming", "ft_enabled", "ieee80211r"
};
@@ -629,10 +629,10 @@ static int apply_vap(struct uci_context *ctx,
}
/* ── Band Steering (802.11k/v) ────────────────────────────────
* 802.11k: Neighbor Reports → el AP informa al cliente qué otros
* APs existen para facilitar el roaming.
* 802.11v: BSS Transition Management → el AP puede "sugerir" al
* cliente que se mueva a otro AP con mejor señal. */
* 802.11k: Neighbor Reports → the AP tells the client what other
* APs exist to facilitate roaming.
* 802.11v: BSS Transition Management → the AP can "suggest" to the
* client to move to another AP with better signal.*/
int band_steer = 0;
if (json_object_object_get_ex(vap_json, "band_steering", &v))
band_steer = json_object_get_boolean(v) ? 1 : 0;
@@ -669,18 +669,18 @@ static int apply_vap(struct uci_context *ctx,
}
/* ═══════════════════════════════════════════════════════════════════
wlan_apply_config — aplicar configuración completa del controlador
wlan_apply_config — apply the controller's full configuration
═══════════════════════════════════════════════════════════════════
Llamado desde inform.c → handle_response() cuando _type=="setstate".
config_json es el JSON completo del controlador.
Called from inform.c → handle_response() when _type=="setstate".
config_json is the controller's complete JSON.
Proceso:
1. Eliminar VAPs antiguas (prefijo openuf_)
2. Aplicar radio_table (canal, potencia, htmode) por radio
3. Crear una VAP por cada entrada en vap_table
4. Hacer commit UCI
5. Ejecutar "wifi reload" para aplicar sin reiniciar el AP
Process:
1. Remove old VAPs (openuf_ prefix)
2. Apply radio_table (channel, power, htmode) per radio
3. Create one VAP for each entry in vap_table
4. Commit UCI
5. Run "wifi reload" to apply without rebooting the AP
*/
int wlan_apply_config(struct json_object *config_json,
const uf_model_t *model)
@@ -689,7 +689,7 @@ int wlan_apply_config(struct json_object *config_json,
json_object_object_get_ex(config_json, "radio_table", &rt_arr);
json_object_object_get_ex(config_json, "vap_table", &vt_arr);
/* Obtener MAC del AP para mobility_domain */
/* Get the AP's MAC for mobility_domain */
char mac_str[32] = "00:00:00:00:00:00";
{
char path[128];
@@ -706,13 +706,13 @@ int wlan_apply_config(struct json_object *config_json,
/* Remove every existing VAP so UniFi becomes the sole Wi-Fi owner. */
wlan_clear();
/* 2. Aplicar radio_table */
/* 2. Apply radio_table */
if (rt_arr && json_object_is_type(rt_arr, json_type_array)) {
int nr = json_object_array_length(rt_arr);
for (int i = 0; i < nr; i++) {
struct json_object *r = json_object_array_get_idx(rt_arr, i);
if (!r) continue;
/* Buscar el device UCI correspondiente a esta banda */
/* Find the UCI device corresponding to this band */
const char *radio_band = "";
if (json_object_object_get_ex(r, "radio", &v))
radio_band = json_object_get_string(v);
@@ -766,7 +766,7 @@ int wlan_apply_config(struct json_object *config_json,
printf("[openuf] Disabled %d default OpenWrt VAPs\n",
disabled_defaults);
/* 3. Crear VAPs */
/* 3. Create VAPs */
if (vt_arr && json_object_is_type(vt_arr, json_type_array)) {
int nv = json_object_array_length(vt_arr);
for (int i = 0; i < nv; i++) {
@@ -974,24 +974,25 @@ int wlan_apply_system_cfg(const char *system_cfg,
}
/* ═══════════════════════════════════════════════════════════════════
wlan_get_vap_table — leer VAPs activas desde UCI
wlan_get_vap_table — read active VAPs from UCI
═══════════════════════════════════════════════════════════════════
Itera todas las wifi-iface con prefijo "openuf_" en /etc/config/wireless
y construye el JSON vap_table para incluir en el payload inform.
Iterates over all wifi-iface entries with the "openuf_" prefix in
/etc/config/wireless and builds the vap_table JSON to include in
the inform payload.
Campos que leemos de UCI → campos en el JSON:
Fields we read from UCI → fields in the JSON:
ssid → essid
device → (usado para buscar radio y BSSID)
device → (used to look up radio and BSSID)
encryption → security (via sec_to_unifi)
hidden → hide_ssid
ieee80211r → fast_roaming_enabled
ieee80211k → band_steering
ieee80211w → pmf_mode ("disabled"/"optional"/"required")
disabled → up (inverso)
disabled → up (inverse)
También intentamos leer el BSSID real de la interfaz wlan
desde /sys/class/net/<iface>/address.
We also try to read the actual BSSID of the wlan interface
from /sys/class/net/<iface>/address.
*/
/* Resolve a configured VAP to the live interface reported by nl80211. */
static int find_runtime_vap(int phy_index, const char *ssid,
@@ -1044,7 +1045,7 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
uci_foreach_element(&pkg->sections, e) {
struct uci_section *sec = uci_to_section(e);
if (strcmp(sec->type, "wifi-iface") != 0) continue;
/* Solo reportar VAPs gestionadas por openuf */
/* Only report VAPs managed by openuf */
if (strncmp(sec->e.name, "openuf_", 7) != 0) continue;
#define UCI_GET(opt) uci_lookup_option_string(ctx, sec, opt)
@@ -1063,7 +1064,7 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
if (!ssid) ssid = "";
if (!device) device = "radio0";
/* Banda de este radio */
/* Band of this radio */
const char *radio_band = "ng";
for (int j = 0; j < model->radio_map_len; j++) {
if (!strcmp(model->radio_map[j].device, device)) {
@@ -1079,7 +1080,7 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
if (find_runtime_vap(ridx, ssid, wlan_iface, sizeof(wlan_iface)) != 0)
snprintf(wlan_iface, sizeof(wlan_iface), "phy%d-ap0", ridx);
/* Leer BSSID real desde sysfs */
/* Read the actual BSSID from sysfs */
char bssid[32] = "00:00:00:00:00:00";
{
char path[128];