Translated all files to ENG #5

Merged
Koda merged 1 commits from fixedTranslations into main 2026-07-12 01:45:38 +01:00
14 changed files with 317 additions and 307 deletions
+4 -4
View File
@@ -1,11 +1,11 @@
# openuf — Makefile para compilar directamente en el dispositivo # openuf — Makefile para compilar directamente en el dispositivo
# #
# Requisitos: # Requirements:
# opkg install gcc make \ # apk add gcc make \
# libmbedtls-dev libuci-dev libjson-c-dev \ # libmbedtls-dev libuci-dev libjson-c-dev \
# lldpd (opcional, para leer vecinos LLDP) # lldpd (optional for LLDP neighbor discovery, UniFi tree view)
# #
# Uso: # Use:
# make -f Makefile.standalone # make -f Makefile.standalone
# make -f Makefile.standalone install # make -f Makefile.standalone install
+1
View File
@@ -81,6 +81,7 @@ The process is automatic:
2. Click on "Adopt" → the controller sends a new key. 2. Click on "Adopt" → the controller sends a new key.
3. The AP applies the key and becomes "Connected". 3. The AP applies the key and becomes "Connected".
4. The controller pushes the WiFi configuration (SSIDs, channels, etc.). 4. The controller pushes the WiFi configuration (SSIDs, channels, etc.).
5. Make sure your WiFi Name doesn't have characters like " - " cause of bad JSON! Grrr
To reset: `rm /etc/openuf/state.json && reboot` To reset: `rm /etc/openuf/state.json && reboot`
+2
View File
@@ -0,0 +1,2 @@
TNBU - TNBU is the magic string/identifier at the start of the binary packet format used in this custom Inform protocol implementation.
CCQ Client Connection Quality
+19 -19
View File
@@ -1,26 +1,26 @@
/* /*
* openuf - announce.c * openuf - announce.c
* *
* Implementa el protocolo de descubrimiento UDP de UniFi (puerto 10001). * Implements the UniFi UDP discovery protocol (port 10001).
* *
* ── Destinos ───────────────────────────────────────────────────────── * ── Discovery Targets ────────────────────────────────────────────────
* El protocolo especifica que los paquetes de anuncio se envían a DOS destinos: * The protocol specifies that discovery packets are sent to TWO targets:
* 1. Broadcast: 255.255.255.255:10001 * 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. * The UniFi controller listens on both addresses.
* Usar sólo broadcast puede fallar en redes donde el broadcast está filtrado. * Using broadcast alone may fail on networks where broadcast traffic is filtered.
* *
* ── Formato del paquete ────────────────────────────────────────────── * ── Packet Format ────────────────────────────────────────────────────
* Header: [0x02][0x06][0x00][total_payload_len] (4 bytes fijos) * Header: [0x02][0x06][0x00][total_payload_len] (fixed 4-byte header)
* TLVs: [type:1][len_hi:1][len_lo:1][value:len] * TLVs: [type:1][len_hi:1][len_lo:1][value:len]
* *
* ── Modelo U6 InWall ───────────────────────────────────────────────── * ── U6 In-Wall Model ─────────────────────────────────────────────────
* Se emula este modelo específicamente porque: * This model is emulated because:
* - Tiene 5 puertos GbE (eth0-eth4): cubre la mayoría de routers OpenWrt * - It provides 5 Gigabit Ethernet ports (eth0-eth4), covering most OpenWrt routers
* - Soporta WiFi 6 (802.11ax) en 2.4 GHz y 5 GHz * - Supports WiFi 6 (802.11ax) on both 2.4 GHz and 5 GHz bands
* - Tiene PoE passthrough (útil para redes de campus) * - Includes PoE passthrough, useful for campus and enterprise deployments
* - Es un modelo actual y bien soportado por el controlador * - Is a current model with excellent UniFi Controller compatibility
*/ */
#include <stdio.h> #include <stdio.h>
@@ -212,9 +212,9 @@ int announce_init(announce_ctx_t *ctx,
}; };
bind(ctx->sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); bind(ctx->sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
/* ── Socket para multicast 233.89.188.1 ────────────────────── */ /* ── Multicast Socket (233.89.188.1) ────────────────────────── */
/* El controlador UniFi también escucha en este grupo multicast. /* The UniFi Controller also listens on this multicast group,
* Esto es necesario cuando broadcast está filtrado en la red. */ * allowing discovery even when broadcast traffic is filtered. */
ctx->sockfd_mcast = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ctx->sockfd_mcast = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ctx->sockfd_mcast >= 0) { if (ctx->sockfd_mcast >= 0) {
int ttl = 1; /* TTL=1: no cruzar router */ int ttl = 1; /* TTL=1: no cruzar router */
@@ -241,7 +241,7 @@ int announce_send(announce_ctx_t *ctx)
int ret = 0; 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 = { struct sockaddr_in dest_bcast = {
.sin_family = AF_INET, .sin_family = AF_INET,
.sin_port = htons(ANNOUNCE_PORT), .sin_port = htons(ANNOUNCE_PORT),
@@ -253,7 +253,7 @@ int announce_send(announce_ctx_t *ctx)
ret = -1; ret = -1;
} }
/* ── Envío 2: Multicast 233.89.188.1:10001 ────────────────── */ /* ── Sending 2: Multicast 233.89.188.1:10001 ────────────────── */
if (ctx->sockfd_mcast >= 0) { if (ctx->sockfd_mcast >= 0) {
struct sockaddr_in dest_mcast = { struct sockaddr_in dest_mcast = {
.sin_family = AF_INET, .sin_family = AF_INET,
+25 -22
View File
@@ -1,11 +1,13 @@
/* /*
* openuf - clients.c * openuf - clients.c
* *
* Enumera clientes para el payload inform → sta_table.
* *
* ── Parseo de iw dev station dump ─────────────────────────────────── * Enumerates clients for the inform payload → sta_table.
*
* ── 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) * Station aa:bb:cc:dd:ee:ff (on wlan0)
* inactive time: 120 ms * inactive time: 120 ms
@@ -18,16 +20,16 @@
* rx bitrate: 108.0 MBit/s * rx bitrate: 108.0 MBit/s
* connected time: 1800 seconds * connected time: 1800 seconds
* *
* Detectamos el inicio de cada cliente con "Station XX:XX:..." y * We detect the start of each client by looking for "Station XX:XX:..."
* rellenamos los campos hasta encontrar el siguiente cliente. * and populate its fields until the next client entry is encountered.
* *
* ── ARP: /proc/net/arp ───────────────────────────────────────────── * ── ARP: /proc/net/arp ─────────────────────────────────────────────
* *
* IP HW type Flags HW addr Mask Device * IP HW type Flags HW addr Mask Device
* 192.168.1.x 0x1 0x2 aa:bb:cc:dd:ee:ff * br-lan * 192.168.1.x 0x1 0x2 aa:bb:cc:dd:ee:ff * br-lan
* *
* Flags 0x2 = entrada completa (reachable). * Flags 0x2 = complete entry (reachable).
* Flags 0x0 = incompleta (no responde ARP), ignorar. * Flags 0x0 = incomplete (no ARP responce), ignore.
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -39,7 +41,7 @@
#include "clients.h" #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) static void mac_lower(const char *src, char *dst, size_t sz)
{ {
for (size_t i = 0; src[i] && i < sz-1; i++) 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; 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) static long parse_rate_kbps(const char *s)
{ {
float r = 0; float r = 0;
@@ -144,7 +146,7 @@ int clients_read_wifi(const char *wlan_iface,
while (fgets(line, sizeof(line), p)) { while (fgets(line, sizeof(line), p)) {
line[strcspn(line, "\r\n")] = '\0'; line[strcspn(line, "\r\n")] = '\0';
/* ── Nueva estación ──────────────────────────────────────── */ /* ── New station ──────────────────────────────────────── */
char mac[32], on_iface[32]; char mac[32], on_iface[32];
if (sscanf(line, "Station %31s (on %31[^)])", mac, on_iface) == 2) { if (sscanf(line, "Station %31s (on %31[^)])", mac, on_iface) == 2) {
if (count >= max_out) break; if (count >= max_out) break;
@@ -159,14 +161,14 @@ int clients_read_wifi(const char *wlan_iface,
} }
if (!cur) continue; if (!cur) continue;
/* ── Contadores ──────────────────────────────────────────── */ /* ── Counters ──────────────────────────────────────────── */
long long llv; long long llv;
if (sscanf(line, " rx bytes: %lld", &llv) == 1) { cur->rx_bytes = llv; continue; } 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, " 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; }
/* ── Sal ───────────────────────────────────────────────── */ /* ── Signal ───────────────────────────────────────────────── */
int sig; int sig;
if (sscanf(line, " signal: %d", &sig) == 1) { cur->signal = sig; continue; } 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; cur->rx_rate = parse_rate_kbps(rest); continue;
} }
/* ── Tiempo conectado ────────────────────────────────────── */ /* ── Connection time ────────────────────────────────────── */
int upt; int upt;
if (sscanf(line, " connected time: %d seconds", &upt) == 1) { if (sscanf(line, " connected time: %d seconds", &upt) == 1) {
cur->uptime = upt; continue; cur->uptime = upt; continue;
@@ -187,7 +189,7 @@ int clients_read_wifi(const char *wlan_iface,
} }
pclose(p); pclose(p);
/* ── Enriquecer: IP, hostname, rssi, CCQ ─────────────────────── */ /* ── Enrich with IP address, hostname, RSSI, and CCQ.─────────────────────── */
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
sta_info_t *s = &out[i]; sta_info_t *s = &out[i];
clients_mac_to_ip(s->mac, s->ip, sizeof(s->ip)); 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]) if (!s->hostname[0])
strncpy(s->hostname, s->mac, sizeof(s->hostname)-1); strncpy(s->hostname, s->mac, sizeof(s->hostname)-1);
/* RSN = SNR estimado (signal - noise) */ /* Estimated SNR (signal - noise). */
s->rssi = s->signal - s->noise; s->rssi = s->signal - s->noise;
if (s->rssi < 0) s->rssi = 0; if (s->rssi < 0) s->rssi = 0;
/* CCQ: métrica 0-1000 /* CCQ: 0-1000 quality metric
* -50 dBm → 1000 (excelente) * -50 dBm → 1000 (excellent)
* -90 dBm → 0 (muy malo) * -90 dBm → 0 (very poor)
* fórmula lineal: (signal + 90) * 25, limitado 0-1000 */ * Linear mapping: (signal + 90) * 25, clamped to the range 0-1000.
*/
int ccq = (s->signal + 90) * 25; int ccq = (s->signal + 90) * 25;
s->ccq = (ccq < 0) ? 0 : (ccq > 1000) ? 1000 : ccq; 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 The resulting JSON array is embedded in vap_table[i].sta_table
en el payload inform. Ejemplo de entrada: within the inform payload. Example input:
{ {
"mac": "aa:bb:cc:dd:ee:ff", "mac": "aa:bb:cc:dd:ee:ff",
"ip": "192.168.1.100", "ip": "192.168.1.100",
+25 -21
View File
@@ -4,31 +4,35 @@
/* /*
* openuf - clients.h * openuf - clients.h
* *
* Enumera clientes conectados (WiFi y ethernet) para el sta_table * Enumerates connected clients (Wi-Fi and Ethernet) for the
* del payload inform. * sta_table in the inform payload.
* *
* ── WiFi: iw dev <iface> station dump ─────────────────────────── * ── Wi-Fi: iw dev <iface> station dump ───────────────────────────
* *
* Por cada cliente asociado devuelve: * Returns the following information for each associated client:
* MAC, señal (dBm), tx/rx bitrate (MBit/s), tx/rx bytes, * MAC address, signal strength (dBm), TX/RX bitrate (Mbit/s),
* tx/rx packets, connected time (segundos) * 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 ─────────────────────────────────── * ── 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) * CCQ = clamp((signal + 90) * 25, 0, 1000)
*/ */
@@ -42,7 +46,7 @@ typedef struct {
char mac[32]; char mac[32];
char ip[64]; char ip[64];
char hostname[64]; char hostname[64];
int signal; /* RSSI dBm (negativo) */ int signal; /* RSSI dBm (negative) */
int noise; /* dBm */ int noise; /* dBm */
int rssi; /* SNR ≈ signal - noise */ int rssi; /* SNR ≈ signal - noise */
long tx_rate; /* kbps */ long tx_rate; /* kbps */
@@ -51,7 +55,7 @@ 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;
int uptime; /* segundos conectado */ int uptime; /* seconds online */
char radio[8]; /* "ng" / "na" / "6g" */ char radio[8]; /* "ng" / "na" / "6g" */
int channel; int channel;
char vap_name[32]; char vap_name[32];
@@ -60,15 +64,15 @@ typedef struct {
bool is_wired; bool is_wired;
} sta_info_t; } 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, int clients_read_wifi(const char *wlan_iface,
const char *radio_band, const char *radio_band,
int channel, int channel,
sta_info_t *out, sta_info_t *out,
int max_out); int max_out);
/* Construye JSON array sta_table para un VAP. /* Creates a JSON array `sta_table` for a VAP.
* El caller debe liberar con json_object_put(). */ * The caller must free it using `json_object_put()` */
struct json_object *clients_build_sta_table(const char *wlan_iface, struct json_object *clients_build_sta_table(const char *wlan_iface,
const char *radio_band, const char *radio_band,
int channel, int channel,
@@ -76,10 +80,10 @@ struct json_object *clients_build_sta_table(const char *wlan_iface,
int vlan_id, int vlan_id,
int is_11r); 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); 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); int clients_mac_to_hostname(const char *mac, char *out, size_t sz);
#endif /* OPENUF_CLIENTS_H */ #endif /* OPENUF_CLIENTS_H */
+85 -85
View File
@@ -1,50 +1,50 @@
/* /*
* openuf - inform.c * 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 * Every 10 seconds the AP makes an HTTP POST to http://<controller>:8080/inform
* con un paquete binario TNBU que contiene JSON cifrado con AES-128-CBC. * with a binary TNBU packet containing JSON encrypted with AES-128-CBC.
* *
* El controlador responde con otro paquete TNBU. El AP descifra, parsea * The controller responds with another TNBU packet. The AP decrypts, parses
* el JSON y ejecuta la acción (_type). * the JSON, and executes the action (_type).
* *
* ── PAQUETE BINARIO TNBU ───────────────────────────────────────────── * ── TNBU BINARY PACKET ───────────────────────────────────────────────
* *
* Offset Bytes Campo * Offset Bytes Field
* ------ ----- ----- * ------ ----- -----
* 0 4 Magic "TNBU" * 0 4 Magic "TNBU"
* 4 4 Versión paquete (=0), uint32 BE * 4 4 Packet version (=0), uint32 BE
* 8 6 MAC del AP * 8 6 AP MAC address
* 14 2 Flags: bit0=cifrado, bit1=zlib * 14 2 Flags: bit0=encrypted, bit1=zlib
* 16 16 IV de AES (cuando cifrado) * 16 16 AES IV (when encrypted)
* 32 4 Versión de datos (=1), uint32 BE * 32 4 Data version (=1), uint32 BE
* 36 4 Longitud del payload, uint32 BE * 36 4 Payload length, uint32 BE
* 40 N Payload JSON, cifrado con AES-128-CBC * 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) * CPU: sysinfo_cpu_percent() → /proc/stat (delta across 2 calls)
* RAM: sysinfo_mem() → /proc/meminfo * RAM: sysinfo_mem() → /proc/meminfo
* Interfaces: sysinfo_iface() → /proc/net/dev + /sys/class/net/ * Interfaces: sysinfo_iface() → /proc/net/dev + /sys/class/net/
* Radios: sysinfo_radio() → iw dev <iface> info + survey * Radios: sysinfo_radio() → iw dev <iface> info + survey
* VAPs UCI: wlan_get_vap_table() → libuci wireless.* * UCI VAPs: wlan_get_vap_table() → libuci wireless.*
* Clientes WiFi: clients_build_sta_table() → iw dev <iface> station dump * WiFi clients: clients_build_sta_table() → iw dev <iface> station dump
* Clientes IP: clients_mac_to_ip() → /proc/net/arp * IP clients: clients_mac_to_ip() → /proc/net/arp
* Clientes nombre: clients_mac_to_hostname() → /tmp/dhcp.leases * Client names: clients_mac_to_hostname() → /tmp/dhcp.leases
* LLDP vecinos: lldp_read_neighbors() → lldpctl -f json * 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 * 1. AP sends inform with key=DEFAULT, default=true, state=1
* 2. Controller responde: {_type:"cmd", cmd:"set-adopt", * 2. Controller responds: {_type:"cmd", cmd:"set-adopt",
* key:"nuevaclave32hex", uri:"http://..."} * key:"new32hexkey", uri:"http://..."}
* 3. AP guarda nueva clave + URL en state.json, adopted=true * 3. AP saves the new key + URL to state.json, adopted=true
* 4. AP envía inform con nueva clave, state=4, default=false * 4. AP sends inform with the new key, state=4, default=false
* 5. Controller responde: {_type:"setstate", radio_table:[...], vap_table:[...]} * 5. Controller responds: {_type:"setstate", radio_table:[...], vap_table:[...]}
* 6. AP aplica config WiFi via wlan_apply_config() → libuci → wifi reload * 6. AP applies WiFi config via wlan_apply_config() → libuci → wifi reload
*/ */
#include <stdio.h> #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. The controller shows CPU and RAM in the device view.
Leemos /proc/stat y /proc/meminfo directamente. We read /proc/stat and /proc/meminfo directly.
*/ */
static struct json_object *build_sys_stats(void) 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)); 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_object_add(o, "cpu",
json_object_new_int(sysinfo_cpu_percent())); 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. All Ethernet ports on the model are reported.
Leemos /proc/net/dev para contadores y /sys/class/net/<iface>/ /proc/net/dev is read for counters, and /sys/class/net/<iface>/
para velocidad, duplex y estado del enlace. for speed, duplex, and link status.
*/ */
static struct json_object *build_if_table(const uf_model_t *m, static struct json_object *build_if_table(const uf_model_t *m,
const openuf_state_t *st) 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. Describes the physical capabilities of each radio to the controller.
El controlador usa esto para saber qué frecuencias y modos soporta. The controller uses this to know which frequencies and modes it supports.
*/ */
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)
@@ -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: Channel utilization is read in real time using:
iw dev wlan0 survey dump → active/busy/tx/rx time iw dev wlan0 survey dump → active/busy/tx/rx time
iw dev wlan0 info → canal actual, potencia iw dev wlan0 info → current channel, power
El controlador muestra estos datos en la vista de RF. The controller displays this data in the RF view.
*/ */
static struct json_object *build_radio_table_stats(const uf_model_t *m) 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++) { 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];
/* Mapear "radio0" → "wlan0" por convención OpenWrt */ /* Map "radio0" → "wlan0" by OpenWrt convention */
char wlan_iface[32]; char wlan_iface[32];
int ridx = 0; int ridx = 0;
sscanf(rm->device, "radio%d", &ridx); sscanf(rm->device, "radio%d", &ridx);
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%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) const char *radio_name = (i < m->radio_table_len)
? m->radio_table[i].name : wlan_iface; ? m->radio_table[i].name : wlan_iface;
int default_ch = (i < m->radio_table_len) 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 /sys/class/net/<iface>/speed and operstate are read to
reflejar el estado real de cada puerto en el controlador. reflect the actual status of each port on the controller.
*/ */
static void build_port_table(struct json_object *root, static void build_port_table(struct json_object *root,
const uf_model_t *m) 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: For each active VAP in UCI:
1. Leemos estasticas de la interfaz wlan con sysinfo_iface() 1. Interface statistics for the wlan are read with sysinfo_iface()
2. Obtenemos el canal actual con sysinfo_radio() 2. The current channel is obtained with sysinfo_radio()
3. Enumeramos clientes con clients_build_sta_table() 3. Clients are enumerated with clients_build_sta_table()
→ iw dev wlan0 station dump (sal, bitrate, bytes, uptime) → iw dev wlan0 station dump (signal, bitrate, bytes, uptime)
→ /proc/net/arp (MAC → IP) → /proc/net/arp (MAC → IP)
→ /tmp/dhcp.leases (MAC → hostname) → /tmp/dhcp.leases (MAC → hostname)
El sta_table anidado es lo que el controlador usa para: The nested sta_table is what the controller uses to:
- Mostrar clientes en el dashboard - Display clients on the dashboard
- Calcular estadísticas por cliente - Calculate per-client statistics
- Dibujar la topología de la red - Draw the network topology
*/ */
static struct json_object *build_vap_table(const uf_model_t *m) 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); struct json_object *uci_vaps = wlan_get_vap_table(m);
int nvaps = json_object_array_length(uci_vaps); 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)) if (json_object_object_get_ex(vap, "fast_roaming_enabled", &v))
is_11r = json_object_get_boolean(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"; char wlan_iface[32] = "phy0-ap0";
if (ifname && ifname[0]) if (ifname && ifname[0])
snprintf(wlan_iface, sizeof(wlan_iface), "%s", ifname); 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; iface_stats_t iface_st;
sysinfo_iface(wlan_iface, &iface_st); sysinfo_iface(wlan_iface, &iface_st);
/* Clientes conectados a esta VAP */ /* Clients connected to this VAP */
struct json_object *sta_tbl = struct json_object *sta_tbl =
clients_build_sta_table(wlan_iface, radio, channel, vap_name, clients_build_sta_table(wlan_iface, radio, channel, vap_name,
vlan_id, is_11r); vlan_id, is_11r);
int num_sta = json_object_array_length(sta_tbl); 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; int tx_pwr = 20;
radio_stats_t rs2; radio_stats_t rs2;
if (sysinfo_radio(wlan_iface, &rs2) == 0 && rs2.tx_power) 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_new_string("user"));
json_object_object_add(o, "ccq", json_object_object_add(o, "ccq",
json_object_new_int(0)); 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_object_add(o, "sta_table", sta_tbl);
json_object_array_add(arr, o); 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, static char *build_payload(const openuf_state_t *st,
const uf_model_t *m, const uf_model_t *m,
long uptime) long uptime)
{ {
/* MAC sin colones → serial (uppercase) */ /* MAC without colons → serial (uppercase) */
char mac_clean[32] = {0}; char mac_clean[32] = {0};
{ {
const char *s = st->mac; int j = 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(); struct json_object *root = json_object_new_object();
/* ── Identidad del dispositivo ──────────────────────────────── */ /* ── Device identity ──────────────────────────────── */
json_object_object_add(root, "mac", json_object_object_add(root, "mac",
json_object_new_string(st->mac)); json_object_new_string(st->mac));
json_object_object_add(root, "serial", json_object_object_add(root, "serial",
@@ -564,17 +564,17 @@ static char *build_payload(const openuf_state_t *st,
/* ── CPU + RAM ──────────────────────────────────────────────── */ /* ── CPU + RAM ──────────────────────────────────────────────── */
json_object_object_add(root, "sys_stats", build_sys_stats()); 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)); 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); build_radio_table(root, m);
/* ── Utilización de canal en tiempo real ────────────────────── */ /* ── Real-time channel utilization ────────────────────── */
json_object_object_add(root, "radio_table_stats", json_object_object_add(root, "radio_table_stats",
build_radio_table_stats(m)); build_radio_table_stats(m));
/* ── Puertos ethernet con estado real ───────────────────────── */ /* ── Ethernet ports with actual status ───────────────────────── */
build_port_table(root, m); build_port_table(root, m);
build_eth_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, "vap_table", vap_table);
json_object_object_add(root, "sta_table", sta_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()); 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_r", json_object_new_int(0));
json_object_object_add(root, "bytes_d", 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)); 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, static unsigned char *build_packet(const char *mac_hex,
const char *key_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, static char *parse_packet(const unsigned char *data, size_t data_len,
const char *key_hex) 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 == "cmd" → set-adopt / reboot / reset / locate
_type == "setstate" → aplicar radio_table + vap_table via UCI _type == "setstate" → apply radio_table + vap_table via UCI
_type == "setparam" → cambiar un parámetro individual _type == "setparam" → change a single parameter
*/ */
static void handle_response(openuf_state_t *st, static void handle_response(openuf_state_t *st,
const uf_model_t *model, const uf_model_t *model,
@@ -896,7 +896,7 @@ static void handle_response(openuf_state_t *st,
system("reboot &"); system("reboot &");
} else if (!strcmp(cmd, "locate")) { } 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"); strcpy(action_out, "locate");
} else { } else {
snprintf(action_out, 64, "cmd:%s", cmd); snprintf(action_out, 64, "cmd:%s", cmd);
@@ -904,7 +904,7 @@ static void handle_response(openuf_state_t *st,
return; return;
} }
/* ── setstate — configuración WiFi del controlador ──────────── */ /* ── setstate — WiFi configuration from the controller ──────────── */
if (!strcmp(type, "setstate")) { if (!strcmp(type, "setstate")) {
if (json_object_object_get_ex(resp, "cfgversion", &v)) if (json_object_object_get_ex(resp, "cfgversion", &v))
snprintf(st->cfgversion, sizeof(st->cfgversion), 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, int inform_send(openuf_state_t *st,
const uf_model_t *model, 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", LOG("Sending inform: adopted=%d, authkey=%.8s..., inform_url=%s",
st->adopted, key_hex, st->inform_url); st->adopted, key_hex, st->inform_url);
/* MAC sin colones */ /* MAC without colons */
char mac_hex[32] = {0}; char mac_hex[32] = {0};
{ {
const char *s = st->mac; int j = 0; const char *s = st->mac; int j = 0;
+28 -29
View File
@@ -1,33 +1,33 @@
/* /*
* openuf - lldp.c * 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: * LLDP TLVs have a 2-byte header:
* bit 15..9 → tipo (7 bits) * bit 15..9 → type (7 bits)
* bit 8..0 → longitud (9 bits, max 511 bytes) * bit 8..0 → length (9 bits, max 511 bytes)
* *
* uint16_t header_be = (type << 9) | (len & 0x1ff) * 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 * header = (1 << 9) | 7 = 0x0207
* → bytes: 0x02 0x07 [subtype=4] [MAC 6 bytes] * → 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)) * 1. socket(AF_PACKET, SOCK_RAW, htons(0x88cc))
* 2. ioctl(SIOCGIFINDEX) → ifindex * 2. ioctl(SIOCGIFINDEX) → ifindex
* 3. Construir frame completo en buffer * 3. Build the complete frame in a buffer
* 4. sendto() con sockaddr_ll * 4. sendto() with sockaddr_ll
* *
* Sin CAP_NET_RAW (no root) → socket() devuelve EPERM. * Without CAP_NET_RAW (not root) → socket() returns EPERM.
* Lo ignoramos silenciosamente (LLDP es opcional). * 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": { * "lldp": {
* "interface": [ * "interface": [
@@ -66,12 +66,12 @@
#include "lldp.h" #include "lldp.h"
/* ─── Constantes ────────────────────────────────────────────────── */ /* ─── Constants ────────────────────────────────────────────────── */
static const uint8_t LLDP_DST[6] = {0x01,0x80,0xc2,0x00,0x00,0x0e}; static const uint8_t LLDP_DST[6] = {0x01,0x80,0xc2,0x00,0x00,0x0e};
#define LLDP_ETHERTYPE 0x88cc #define LLDP_ETHERTYPE 0x88cc
#define CAP_WLAN_AP 0x0040 #define CAP_WLAN_AP 0x0040
/* ─── Escribir TLV en buffer ────────────────────────────────────── */ /* ─── Write TLV to buffer ────────────────────────────────────── */
static int tlv_write(uint8_t *buf, int pos, int maxlen, static int tlv_write(uint8_t *buf, int pos, int maxlen,
int type, const uint8_t *val, int vlen) 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)); (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]) static void parse_mac(const char *s, uint8_t out[6])
{ {
unsigned int b[6]={0}; unsigned int b[6]={0};
@@ -109,7 +109,7 @@ int lldp_send_frame(const char *ifname,
{ {
/* Socket raw — requiere root */ /* Socket raw — requiere root */
int fd = socket(AF_PACKET, SOCK_RAW, htons(LLDP_ETHERTYPE)); 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; struct ifreq ifr;
memset(&ifr, 0, sizeof(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 >> 8),
0x00, (uint8_t)(CAP_WLAN_AP & 0xff) 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; 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; uint16_t cap = CAP_WLAN_AP;
v[0] = (cap >> 8) & 0xff; v[1] = cap & 0xff; v[0] = (cap >> 8) & 0xff; v[1] = cap & 0xff;
v[2] = v[0]; v[3] = v[1]; /* enabled = same */ 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. Navigate: root → "lldp" → "interface" (array) → each neighbor.
Por cada vecino extrae: chassis.id, chassis.name, chassis.descr, For each neighbor, extract: chassis.id, chassis.name, chassis.descr,
port.id, port.descr, y el nombre de la interfaz local. port.id, port.descr, and the name of the local interface.
The result is included in lldp_table[] of the inform payload.
El resultado se incluye en lldp_table[] del payload inform. The controller uses it to draw the connection lines in
El controlador lo usa para dibujar las líneas de conexión en the visual topology (which switch/port this AP connects to).
la topología visual (qué switch/puerto conecta a este AP).
*/ */
struct json_object *lldp_read_neighbors(void) 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); struct json_object *root = json_tokener_parse(buf);
if (!root) return result; if (!root) return result;
/* Navegar: root.lldp.interface[] */ /* Browse: root.lldp.interface[] */
struct json_object *lldp_o, *iface_arr; 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(root, "lldp", &lldp_o)) goto done;
if (!json_object_object_get_ex(lldp_o, "interface", &iface_arr)) 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); struct json_object *iface = json_object_array_get_idx(iface_arr, i);
if (!iface) continue; if (!iface) continue;
/* Puerto local */ /* Local port */
struct json_object *tmp_o; struct json_object *tmp_o;
const char *local_port = ""; const char *local_port = "";
if (json_object_object_get_ex(iface, "name", &tmp_o)) 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) * 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. * The AP transmits LLDP frames on each Ethernet port.
* Esto permite al switch upstream registrar al AP como vecino, * This allows the upstream switch to register the AP as a neighbor,
* y al controlador UniFi construir el mapa de topología visual. * and lets the UniFi controller build the visual topology map.
* *
* Frame Ethernet: * 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 * src = MAC del AP
* type = 0x88cc * type = 0x88cc
* *
* Payload (TLVs encadenados): * Payload (TLVs chained):
* Header TLV = [type:7bits | len_hi:1bit][len_lo:8bits] * Header TLV = [type:7bits | len_hi:1bit][len_lo:8bits]
* *
* TLV type=1 Chassis ID subtype=4(MAC), value=MAC[6] * 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=7 Capabilities cap=0x0040(WLAN-AP), en=0x0040
* TLV type=0 End of LLDPDU len=0 * 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 * If lldpd is installed, the detected neighbors are read
* y los incluimos en lldp_table del payload inform. * and included in lldp_table of the inform payload.
* *
* lldp_table en el JSON inform: * lldp_table in the inform JSON:
* [{ * [{
* "local_port": "eth0", * "local_port": "eth0",
* "chassis_id": "aa:bb:cc:...", * "chassis_id": "aa:bb:cc:...",
@@ -43,30 +43,30 @@
* "port_desc": "to-AP" * "port_desc": "to-AP"
* }] * }]
* *
* ── SIN lldpd ─────────────────────────────────────────────────── * ── Without lldpd ───────────────────────────────────────────────────
* *
* lldp_send_frame() funciona sin lldpd (usa raw socket directo). * lldp_send_frame() works without lldpd (it uses a direct raw socket).
* lldp_read_neighbors() retorna array vacío si no hay lldpctl. * lldp_read_neighbors() returns an empty array if lldpctl is not present.
*/ */
#include <stdbool.h> #include <stdbool.h>
#include <json-c/json.h> #include <json-c/json.h>
/* Envía un frame LLDP por raw socket AF_PACKET. /* Sends an LLDP frame via raw AF_PACKET socket.
* Requiere ejecutar como root (CAP_NET_RAW). * Requires running as root (CAP_NET_RAW).
* Devuelve 0 si ok, -1 si error (sin root → error silencioso). */ * Returns 0 on success, -1 on error (without root → silent error). */
int lldp_send_frame(const char *ifname, int lldp_send_frame(const char *ifname,
const char *mac_str, const char *mac_str,
const char *hostname, const char *hostname,
const char *model_desc, const char *model_desc,
int ttl); int ttl);
/* Lee vecinos LLDP de lldpctl y retorna JSON array lldp_table. /* Reads LLDP neighbors from lldpctl and returns a JSON array lldp_table.
* Si lldpctl no está, retorna array vacío (no falla). * If lldpctl is not present, returns an empty array (does not fail).
* Caller libera con json_object_put(). */ * Caller frees it with json_object_put(). */
struct json_object *lldp_read_neighbors(void); struct json_object *lldp_read_neighbors(void);
/* true si lldpctl está instalado */ /* true if lldpctl is installed */
bool lldp_available(void); bool lldp_available(void);
#endif /* OPENUF_LLDP_H */ #endif /* OPENUF_LLDP_H */
+9 -9
View File
@@ -1,10 +1,10 @@
/* /*
* openuf - main.c * openuf - main.c
* *
* Daemon principal. Bucle con tres tareas: * Main daemon. Loop with three tasks:
* 1. Announce UDP broadcast+multicast cada 10s (descubrimiento L2) * 1. Announce UDP broadcast+multicast each 10s (discovery L2)
* 2. Inform HTTP POST cifrado cada 10s (adopción + telemetría) * 2. Inform HTTP POST cifrado each 10s (adoption + telemetrics)
* 3. LLDP Raw frame L2 cada 30s (topología visual en UniFi) * 3. LLDP Raw frame L2 each 30s (visual topology in UniFi)
*/ */
#include <stdio.h> #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]; char lldp_desc[128];
snprintf(lldp_desc, sizeof(lldp_desc), snprintf(lldp_desc, sizeof(lldp_desc),
"%s %s%s (openuf)", "%s %s%s (openuf)",
model->model_display, model->fw_pre, model->fw_ver); model->model_display, model->fw_pre, model->fw_ver);
/* ── Bucle principal ─────────────────────────────────────────── */ /* ── Main loop ─────────────────────────────────────────── */
time_t start_time = time(NULL); time_t start_time = time(NULL);
time_t last_announce = 0; time_t last_announce = 0;
time_t last_inform = 0; time_t last_inform = 0;
@@ -154,12 +154,12 @@ int main(int argc, char *argv[])
last_announce = now; last_announce = now;
} }
/* LLDP frames por cada interfaz ethernet */ /* LLDP frames for each Ethernet interface */
if ((now - last_lldp) >= LLDP_INTERVAL) { if ((now - last_lldp) >= LLDP_INTERVAL) {
LOG("Sending LLDP frames"); LOG("Sending LLDP frames");
for (int i = 0; i < model->port_table_len; i++) { for (int i = 0; i < model->port_table_len; i++) {
const char *iface = model->port_table[i].ifname; 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]; char iface_mac[32];
if (get_mac(iface, iface_mac, sizeof(iface_mac)) != 0) if (get_mac(iface, iface_mac, sizeof(iface_mac)) != 0)
strncpy(iface_mac, mac_str, sizeof(iface_mac)-1); strncpy(iface_mac, mac_str, sizeof(iface_mac)-1);
@@ -178,7 +178,7 @@ int main(int argc, char *argv[])
last_inform = now; last_inform = now;
LOG("Sending inform"); LOG("Sending inform");
/* Actualizar IP en cada ciclo */ /* Update IP each cycle */
char new_ip[64] = {0}; char new_ip[64] = {0};
if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 || if (get_ip(cfg.lan_if, new_ip, sizeof(new_ip)) == 0 ||
get_ip("eth0", 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, .radio_map=uapg2aclr_rmap, .radio_map_len=2,
}; };
/* ─── Registro de modelos ─────────────────────────────────────── */ /* ─── Model Registry ─────────────────────────────────────── */
static const uf_model_t *all_models[] = { static const uf_model_t *all_models[] = {
&model_u6inwall, &model_u6inwall,
&model_u6lite, &model_u6lite,
+23 -23
View File
@@ -1,26 +1,26 @@
/* /*
* openuf - sysinfo.c * openuf - sysinfo.c
* *
* Lee estasticas del sistema para el payload inform. * Reads system statistics for the inform payload.
* *
* ── CPU: /proc/stat ────────────────────────────────────────────────── * ── 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: * Usage is calculated with two snapshots taken at different times:
* activo = user + nice + system + irq + softirq + steal * active = user + nice + system + irq + softirq + steal
* total = activo + idle + iowait * total = active + idle + iowait
* uso % = (Δactivo / Δtotal) × 100 * usage % = (Δactive / Δtotal) × 100
* *
* ── Memoria: /proc/meminfo ─────────────────────────────────────────── * ── Memory: /proc/meminfo ───────────────────────────────────────────
* *
* MemTotal, MemFree, Buffers, Cached * MemTotal, MemFree, Buffers, Cached
* used = total - free - buffers - cached * used = total - free - buffers - cached
* *
* ── Interfaces: /proc/net/dev + /sys/class/net/<iface>/ ───────────── * ── Interfaces: /proc/net/dev + /sys/class/net/<iface>/ ─────────────
* *
* /proc/net/dev → contadores acumulados rx/tx * /proc/net/dev → cumulative rx/tx counters
* /sys/class/net/speed → velocidad negociada (Mbps) * /sys/class/net/speed → negotiated speed (Mbps)
* /sys/class/net/duplex → "full" / "half" * /sys/class/net/duplex → "full" / "half"
* /sys/class/net/operstate → "up" / "down" / "unknown" * /sys/class/net/operstate → "up" / "down" / "unknown"
* /sys/class/net/address → MAC * /sys/class/net/address → MAC
@@ -28,8 +28,8 @@
* *
* ── Radio: iw dev <iface> info + survey dump ───────────────────────── * ── Radio: iw dev <iface> info + survey dump ─────────────────────────
* *
* info: canal actual, potencia TX * info: current channel, TX power
* survey dump: active/busy/tx/rx time → calcular % utilización * survey dump: active/busy/tx/rx time → calculate % utilization
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -47,7 +47,7 @@
#include "sysinfo.h" #include "sysinfo.h"
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
Memoria Memory
═══════════════════════════════════════════════════════════════════ */ ═══════════════════════════════════════════════════════════════════ */
int sysinfo_mem(mem_stats_t *out) 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, static int read_sysfs_str(const char *iface, const char *file,
char *out, size_t sz) char *out, size_t sz)
@@ -167,7 +167,7 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
/* IP */ /* IP */
read_ip_ioctl(ifname, out->ip, sizeof(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"); FILE *f = fopen("/proc/net/dev", "r");
if (!f) return 0; if (!f) return 0;
@@ -179,7 +179,7 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
char *colon = strchr(line, ':'); char *colon = strchr(line, ':');
if (!colon) continue; if (!colon) continue;
/* Extraer nombre de interfaz (puede tener espacios al inicio) */ /* Extract interface name (may have leading spaces) */
size_t end = colon - line; size_t end = colon - line;
while (end > 0 && line[end-1] == ' ') end--; while (end > 0 && line[end-1] == ' ') end--;
size_t start = 0; 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 1. iw dev wlan0 info → channel and power
Ejemplo: Example:
Interface wlan0 Interface wlan0
channel 6 (2437 MHz), width: 20 MHz channel 6 (2437 MHz), width: 20 MHz
txpower 20.00 dBm txpower 20.00 dBm
2. iw dev wlan0 survey dump → utilización del canal 2. iw dev wlan0 survey dump → channel utilization
Buscamos el bloque con "[in use]": We look for the block with "[in use]":
frequency: 2437 MHz [in use] frequency: 2437 MHz [in use]
channel active time: 12345 ms channel active time: 12345 ms
channel busy time: 987 ms channel busy time: 987 ms
channel transmit time: 456 ms channel transmit time: 456 ms
channel receive time: 321 ms channel receive time: 321 ms
Calculamos: We calculate:
cu_total = busy/active × 100 cu_total = busy/active × 100
cu_self_tx = transmit/active × 100 cu_self_tx = transmit/active × 100
cu_self_rx = receive/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; in_use = 1; active=busy=tx_t=rx_t=0; continue;
} }
if (!in_use) 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]")) { if (strstr(line, "frequency:") && !strstr(line, "[in use]")) {
in_use = 0; continue; 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); out->cu_self_rx = (int)(rx_t * 100 / active);
} }
/* Número de clientes asociados */ /* Number of associated clients */
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
"iw dev %s station dump 2>/dev/null | grep -c '^Station'", "iw dev %s station dump 2>/dev/null | grep -c '^Station'",
iface); iface);
+14 -14
View File
@@ -4,15 +4,15 @@
/* /*
* openuf - sysinfo.h * openuf - sysinfo.h
* *
* Lee estasticas del sistema (CPU, RAM, interfaces, radios). * Reads system statistics (CPU, RAM, interfaces, radios).
* Todas las lecturas son del kernel Linux directamente: * All readings come directly from the Linux kernel:
* *
* /proc/stat → uso CPU (deltas entre dos snapshots) * /proc/stat → CPU usage (deltas between two snapshots)
* /proc/meminfo → memoria total/libre/buffer/cache * /proc/meminfo → total/free/buffer/cache memory
* /proc/net/dev → contadores rx/tx por interfaz * /proc/net/dev → rx/tx counters per interface
* /sys/class/net/ → speed, duplex, operstate, MAC * /sys/class/net/ → speed, duplex, operstate, MAC
* iw dev <if> info → canal actual, potencia TX * iw dev <if> info → current channel, TX power
* iw dev <if> survey dump → utilización del canal * iw dev <if> survey dump → channel utilization
*/ */
#include <stdbool.h> #include <stdbool.h>
@@ -28,9 +28,9 @@ typedef struct {
int sysinfo_mem(mem_stats_t *out); int sysinfo_mem(mem_stats_t *out);
/* ── CPU ─────────────────────────────────────────────────────────── */ /* ── CPU ─────────────────────────────────────────────────────────── */
/* Retorna % uso CPU (0-100). Primera llamada retorna 0 (toma snapshot). /* Returns % CPU usage (0-100). The first call returns 0 (takes a snapshot).
* Las siguientes calculan el delta respecto a la anterior. * Subsequent calls compute the delta relative to the previous one.
* Con intervalo de 10s da un buen promedio de uso. */ * With a 10s interval this gives a good average of usage. */
int sysinfo_cpu_percent(void); int sysinfo_cpu_percent(void);
/* ── Interfaz de red ─────────────────────────────────────────────── */ /* ── Interfaz de red ─────────────────────────────────────────────── */
@@ -39,7 +39,7 @@ typedef struct {
char mac[32]; char mac[32];
char ip[64]; char ip[64];
bool up; 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; bool full_duplex;
long long rx_bytes; long long rx_bytes;
long long tx_bytes; long long tx_bytes;
@@ -60,9 +60,9 @@ typedef struct {
char iface[32]; char iface[32];
int channel; int channel;
int tx_power; int tx_power;
int cu_total; /* % uso canal total */ int cu_total; /* % total channel usage */
int cu_self_tx; /* % tiempo transmitiendo */ int cu_self_tx; /* % time spent transmitting */
int cu_self_rx; /* % tiempo recibiendo */ int cu_self_rx; /* % time spent receiving */
int num_sta; int num_sta;
int noise; /* dBm */ int noise; /* dBm */
} radio_stats_t; } radio_stats_t;
+49 -48
View File
@@ -373,7 +373,7 @@ void wlan_clear(void)
return; return;
} }
/* Recopilar secciones a eliminar (no modificar durante iteración) */ /* Collect sections to remove (do not modify during iteration) */
char *to_del[64]; char *to_del[64];
int ndel = 0; int ndel = 0;
struct uci_element *e; 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 channel → wireless.<device>.channel
ht → wireless.<device>.htmode ("HT20" / "HT40" / "HT80" / "HE80") ht → wireless.<device>.htmode ("HT20" / "HT40" / "HT80" / "HE80")
tx_power → wireless.<device>.txpower 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, void wlan_apply_radio(struct json_object *radio_json,
const char *device_name) const char *device_name)
@@ -452,7 +452,7 @@ void wlan_apply_radio(struct json_object *radio_json,
RP("ht", "htmode"); RP("ht", "htmode");
/* Canal: 0 = auto en 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);
if (ch == 0) { if (ch == 0) {
@@ -474,7 +474,7 @@ void wlan_apply_radio(struct json_object *radio_json,
uci_set(ctx, &ptr); uci_set(ctx, &ptr);
} }
/* Habilitar el radio */ /* Enable the radio */
snprintf(path, sizeof(path), "wireless.%s.disabled=0", device_name); snprintf(path, sizeof(path), "wireless.%s.disabled=0", device_name);
struct uci_ptr ptr; struct uci_ptr ptr;
if (uci_lookup_ptr(ctx, &ptr, path, true) == UCI_OK) 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 essid → wireless.openuf_X.ssid
x_passphrase → wireless.openuf_X.key x_passphrase → wireless.openuf_X.key
security → wireless.openuf_X.encryption (via sec_to_uci) security → wireless.openuf_X.encryption (via sec_to_uci)
hide_ssid → wireless.openuf_X.hidden hide_ssid → wireless.openuf_X.hidden
guest_policy → wireless.openuf_X.isolate (aislamiento de clientes) guest_policy → wireless.openuf_X.isolate (client isolation)
fast_roaming_enabled → ieee80211r, ft_over_ds, mobility_domain, ft_psk_generate_local fast_roaming_enabled → ieee80211r, ft_over_ds, mobility_domain, ft_psk_generate_local
band_steering → ieee80211k, ieee80211v, rrm_neighbor_report, bss_transition band_steering → ieee80211k, ieee80211v, rrm_neighbor_report, bss_transition
pmf_mode → ieee80211w (0/1/2) pmf_mode → ieee80211w (0/1/2)
wpa3_support → añadir "sae-mixed" si WPA2+WPA3 wpa3_support → add "sae-mixed" if WPA2+WPA3
uapsd → uapsd (U-APSD power saving) uapsd → uapsd (U-APSD power saving)
vlan_id → wireless.openuf_X.vlan_id (si ≠ 0) vlan_id → wireless.openuf_X.vlan_id (if ≠ 0)
*/ */
static int apply_vap(struct uci_context *ctx, static int apply_vap(struct uci_context *ctx,
struct uci_package *pkg, 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); 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}; char safe[16] = {0};
safe_section_name(essid, safe, sizeof(safe)); safe_section_name(essid, safe, sizeof(safe));
char sec_name[48]; char sec_name[48];
@@ -571,23 +571,23 @@ static int apply_vap(struct uci_context *ctx,
if (vap_id) if (vap_id)
UCI_SET(ctx, "wireless", sec_name, "openuf_vap_id", vap_id); UCI_SET(ctx, "wireless", sec_name, "openuf_vap_id", vap_id);
/* Contraseña */ /* Password */
if (pass && pass[0] && strcmp(security,"open") != 0) if (pass && pass[0] && strcmp(security,"open") != 0)
UCI_SET(ctx, "wireless", sec_name, "key", pass); UCI_SET(ctx, "wireless", sec_name, "key", pass);
/* SSID oculto */ /* hidden SSID */
int hidden = 0; int hidden = 0;
if (json_object_object_get_ex(vap_json, "hide_ssid", &v)) if (json_object_object_get_ex(vap_json, "hide_ssid", &v))
hidden = json_object_get_boolean(v) ? 1 : 0; hidden = json_object_get_boolean(v) ? 1 : 0;
UCI_SET_INT(ctx, "wireless", sec_name, "hidden", hidden); UCI_SET_INT(ctx, "wireless", sec_name, "hidden", hidden);
/* Aislamiento de clientes (guest network) */ /* Client isolation (guest network) */
int isolate = 0; int isolate = 0;
if (json_object_object_get_ex(vap_json, "guest_policy", &v)) if (json_object_object_get_ex(vap_json, "guest_policy", &v))
isolate = json_object_get_boolean(v) ? 1 : 0; isolate = json_object_get_boolean(v) ? 1 : 0;
UCI_SET_INT(ctx, "wireless", sec_name, "isolate", isolate); 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; int uapsd = 1;
if (json_object_object_get_ex(vap_json, "uapsd", &v)) if (json_object_object_get_ex(vap_json, "uapsd", &v))
uapsd = json_object_get_boolean(v) ? 1 : 0; 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) ────────────── /* ── PMF (Protected Management Frames / 802.11w) ──────────────
* "disabled" → 0, "optional" → 1, "required" → 2 * "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; int pmf = 0;
if (json_object_object_get_ex(vap_json, "pmf_mode", &v)) { if (json_object_object_get_ex(vap_json, "pmf_mode", &v)) {
const char *pm = json_object_get_string(v); const char *pm = json_object_get_string(v);
if (!strcmp(pm, "optional")) pmf = 1; if (!strcmp(pm, "optional")) pmf = 1;
if (!strcmp(pm, "required")) pmf = 2; if (!strcmp(pm, "required")) pmf = 2;
} }
/* WPA3 obliga PMF=2 */ /* WPA3 forces PMF=2 */
if (!strcmp(security,"wpa3") || !strcmp(security,"wpa3transition") || if (!strcmp(security,"wpa3") || !strcmp(security,"wpa3transition") ||
!strcmp(security,"wpa3enterprise")) !strcmp(security,"wpa3enterprise"))
pmf = 2; pmf = 2;
UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211w", pmf); UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211w", pmf);
/* ── Fast Roaming (802.11r FT) ──────────────────────────────── /* ── Fast Roaming (802.11r FT) ────────────────────────────────
* Permite que los clientes se muevan entre APs sin re-autenticación * Allows clients to move between APs without re-authentication
* completa. El handshake FT sólo tarda ~50ms vs ~200-300ms normal. */ * complete. The FT handshake only takes ~50ms vs ~200-300ms for a normal one. */
const char *ft_keys[] = { const char *ft_keys[] = {
"fast_roaming_enabled", "fast_roaming", "ft_enabled", "ieee80211r" "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) ──────────────────────────────── /* ── Band Steering (802.11k/v) ────────────────────────────────
* 802.11k: Neighbor Reports → el AP informa al cliente qué otros * 802.11k: Neighbor Reports → the AP tells the client what other
* APs existen para facilitar el roaming. * APs exist to facilitate roaming.
* 802.11v: BSS Transition Management → el AP puede "sugerir" al * 802.11v: BSS Transition Management → the AP can "suggest" to the
* cliente que se mueva a otro AP con mejor señal. */ * client to move to another AP with better signal.*/
int band_steer = 0; int band_steer = 0;
if (json_object_object_get_ex(vap_json, "band_steering", &v)) if (json_object_object_get_ex(vap_json, "band_steering", &v))
band_steer = json_object_get_boolean(v) ? 1 : 0; 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". Called from inform.c → handle_response() when _type=="setstate".
config_json es el JSON completo del controlador. config_json is the controller's complete JSON.
Proceso: Process:
1. Eliminar VAPs antiguas (prefijo openuf_) 1. Remove old VAPs (openuf_ prefix)
2. Aplicar radio_table (canal, potencia, htmode) por radio 2. Apply radio_table (channel, power, htmode) per radio
3. Crear una VAP por cada entrada en vap_table 3. Create one VAP for each entry in vap_table
4. Hacer commit UCI 4. Commit UCI
5. Ejecutar "wifi reload" para aplicar sin reiniciar el AP 5. Run "wifi reload" to apply without rebooting the AP
*/ */
int wlan_apply_config(struct json_object *config_json, int wlan_apply_config(struct json_object *config_json,
const uf_model_t *model) 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, "radio_table", &rt_arr);
json_object_object_get_ex(config_json, "vap_table", &vt_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 mac_str[32] = "00:00:00:00:00:00";
{ {
char path[128]; 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. */ /* Remove every existing VAP so UniFi becomes the sole Wi-Fi owner. */
wlan_clear(); wlan_clear();
/* 2. Aplicar radio_table */ /* 2. Apply radio_table */
if (rt_arr && json_object_is_type(rt_arr, json_type_array)) { if (rt_arr && json_object_is_type(rt_arr, json_type_array)) {
int nr = json_object_array_length(rt_arr); int nr = json_object_array_length(rt_arr);
for (int i = 0; i < nr; i++) { for (int i = 0; i < nr; i++) {
struct json_object *r = json_object_array_get_idx(rt_arr, i); struct json_object *r = json_object_array_get_idx(rt_arr, i);
if (!r) continue; if (!r) continue;
/* Buscar el device UCI correspondiente a esta banda */ /* Find the UCI device corresponding to this band */
const char *radio_band = ""; const char *radio_band = "";
if (json_object_object_get_ex(r, "radio", &v)) if (json_object_object_get_ex(r, "radio", &v))
radio_band = json_object_get_string(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", printf("[openuf] Disabled %d default OpenWrt VAPs\n",
disabled_defaults); disabled_defaults);
/* 3. Crear VAPs */ /* 3. Create VAPs */
if (vt_arr && json_object_is_type(vt_arr, json_type_array)) { if (vt_arr && json_object_is_type(vt_arr, json_type_array)) {
int nv = json_object_array_length(vt_arr); int nv = json_object_array_length(vt_arr);
for (int i = 0; i < nv; i++) { 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 Iterates over all wifi-iface entries with the "openuf_" prefix in
y construye el JSON vap_table para incluir en el payload inform. /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 ssid → essid
device → (usado para buscar radio y BSSID) device → (used to look up radio and BSSID)
encryption → security (via sec_to_unifi) encryption → security (via sec_to_unifi)
hidden → hide_ssid hidden → hide_ssid
ieee80211r → fast_roaming_enabled ieee80211r → fast_roaming_enabled
ieee80211k → band_steering ieee80211k → band_steering
ieee80211w → pmf_mode ("disabled"/"optional"/"required") ieee80211w → pmf_mode ("disabled"/"optional"/"required")
disabled → up (inverso) disabled → up (inverse)
También intentamos leer el BSSID real de la interfaz wlan We also try to read the actual BSSID of the wlan interface
desde /sys/class/net/<iface>/address. from /sys/class/net/<iface>/address.
*/ */
/* Resolve a configured VAP to the live interface reported by nl80211. */ /* Resolve a configured VAP to the live interface reported by nl80211. */
static int find_runtime_vap(int phy_index, const char *ssid, 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) { uci_foreach_element(&pkg->sections, e) {
struct uci_section *sec = uci_to_section(e); struct uci_section *sec = uci_to_section(e);
if (strcmp(sec->type, "wifi-iface") != 0) continue; 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; if (strncmp(sec->e.name, "openuf_", 7) != 0) continue;
#define UCI_GET(opt) uci_lookup_option_string(ctx, sec, opt) #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 (!ssid) ssid = "";
if (!device) device = "radio0"; if (!device) device = "radio0";
/* Banda de este radio */ /* Band of this radio */
const char *radio_band = "ng"; const char *radio_band = "ng";
for (int j = 0; j < model->radio_map_len; j++) { for (int j = 0; j < model->radio_map_len; j++) {
if (!strcmp(model->radio_map[j].device, device)) { 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) if (find_runtime_vap(ridx, ssid, wlan_iface, sizeof(wlan_iface)) != 0)
snprintf(wlan_iface, sizeof(wlan_iface), "phy%d-ap0", ridx); 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 bssid[32] = "00:00:00:00:00:00";
{ {
char path[128]; char path[128];