diff --git a/Makefile.standalone b/Makefile.standalone index 07a0657..55c4178 100644 --- a/Makefile.standalone +++ b/Makefile.standalone @@ -1,11 +1,11 @@ -# openuf — Makefile para compilar directamente en el dispositivo +# openuf — Makefile to compile directly on the device # -# Requisitos: -# opkg install gcc make \ +# Requirements: +# apk add gcc make \ # 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 install diff --git a/README.md b/README.md index 5d24f6a..b281071 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ The process is automatic: 2. Click on "Adopt" → the controller sends a new key. 3. The AP applies the key and becomes "Connected". 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` diff --git a/legend.md b/legend.md new file mode 100644 index 0000000..cc5191f --- /dev/null +++ b/legend.md @@ -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 \ No newline at end of file diff --git a/src/announce.c b/src/announce.c index 612cef0..5c6b5f2 100644 --- a/src/announce.c +++ b/src/announce.c @@ -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 @@ -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, diff --git a/src/clients.c b/src/clients.c index 73311eb..3505d2c 100644 --- a/src/clients.c +++ b/src/clients.c @@ -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; } - /* ── Señal ───────────────────────────────────────────────── */ + /* ── 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", diff --git a/src/clients.h b/src/clients.h index 35a2565..338831f 100644 --- a/src/clients.h +++ b/src/clients.h @@ -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 station dump ──────────────────────────── + * ── Wi-Fi: iw dev 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 0–1000 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 */ diff --git a/src/inform.c b/src/inform.c index 11c7a93..ec46dca 100644 --- a/src/inform.c +++ b/src/inform.c @@ -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://: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://: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 info + survey - * VAPs UCI: wlan_get_vap_table() → libuci wireless.* - * Clientes WiFi: clients_build_sta_table() → iw dev 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 info + survey + * UCI VAPs: wlan_get_vap_table() → libuci wireless.* + * WiFi clients: clients_build_sta_table() → iw dev 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 @@ -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// - 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// + 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//speed y operstate para - reflejar el estado real de cada puerto en el controlador. + /sys/class/net//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 estadísticas 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 (señal, 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; diff --git a/src/lldp.c b/src/lldp.c index ad2f59d..38ed6d3 100644 --- a/src/lldp.c +++ b/src/lldp.c @@ -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)) diff --git a/src/lldp.h b/src/lldp.h index c6f1b87..0fea786 100644 --- a/src/lldp.h +++ b/src/lldp.h @@ -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 #include -/* 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 */ diff --git a/src/main.c b/src/main.c index b9d4462..8704e1b 100644 --- a/src/main.c +++ b/src/main.c @@ -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 @@ -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) diff --git a/src/models.c b/src/models.c index f17076b..6fa3263 100644 --- a/src/models.c +++ b/src/models.c @@ -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, diff --git a/src/sysinfo.c b/src/sysinfo.c index ff904b0..aee917a 100644 --- a/src/sysinfo.c +++ b/src/sysinfo.c @@ -1,26 +1,26 @@ /* * openuf - sysinfo.c * - * Lee estadísticas 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// ───────────── * - * /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 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); diff --git a/src/sysinfo.h b/src/sysinfo.h index 068eb76..983d2dc 100644 --- a/src/sysinfo.h +++ b/src/sysinfo.h @@ -4,15 +4,15 @@ /* * openuf - sysinfo.h * - * Lee estadísticas 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 info → canal actual, potencia TX - * iw dev survey dump → utilización del canal + * iw dev info → current channel, TX power + * iw dev survey dump → channel utilization */ #include @@ -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; diff --git a/src/wlan.c b/src/wlan.c index fbb4204..66f09f4 100644 --- a/src/wlan.c +++ b/src/wlan.c @@ -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..channel ht → wireless..htmode ("HT20" / "HT40" / "HT80" / "HE80") tx_power → wireless..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__ */ + /* Section name: openuf__ */ 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//address. + We also try to read the actual BSSID of the wlan interface + from /sys/class/net//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];