Fixed translations #10
+5
-5
@@ -1,11 +1,11 @@
|
|||||||
# openuf — Makefile para compilar directamente en el dispositivo
|
# openuf — Makefile to compile directly on the device
|
||||||
#
|
#
|
||||||
# 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
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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; }
|
||||||
|
|
||||||
/* ── Señal ───────────────────────────────────────────────── */
|
/* ── 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
@@ -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 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)
|
* 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
@@ -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 estadísticas 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 (señal, 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,26 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
* openuf - sysinfo.c
|
* openuf - sysinfo.c
|
||||||
*
|
*
|
||||||
* Lee estadísticas 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
@@ -4,15 +4,15 @@
|
|||||||
/*
|
/*
|
||||||
* openuf - sysinfo.h
|
* openuf - sysinfo.h
|
||||||
*
|
*
|
||||||
* Lee estadísticas 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
@@ -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];
|
||||||
|
|||||||
Reference in New Issue
Block a user