fixing adoption with invalid WiFi Name scheme

This commit is contained in:
2026-07-11 21:57:11 +00:00
parent 3dca4afea3
commit 6b06e57a4c
4 changed files with 177 additions and 50 deletions
+9 -2
View File
@@ -239,7 +239,9 @@ int clients_read_wifi(const char *wlan_iface,
struct json_object *clients_build_sta_table(const char *wlan_iface,
const char *radio_band,
int channel,
const char *vap_name)
const char *vap_name,
int vlan_id,
int is_11r)
{
sta_info_t stas[MAX_STA];
int n = clients_read_wifi(wlan_iface, radio_band, channel,
@@ -266,7 +268,12 @@ struct json_object *clients_build_sta_table(const char *wlan_iface,
json_object_object_add(o, "channel", json_object_new_int(s->channel));
json_object_object_add(o, "vap_name", json_object_new_string(
vap_name ? vap_name : wlan_iface));
json_object_object_add(o, "is_11r", json_object_new_boolean(s->is_11r));
json_object_object_add(o, "is_11r", json_object_new_boolean(is_11r));
if (vlan_id > 0) {
json_object_object_add(o, "vlan", json_object_new_int(vlan_id));
json_object_object_add(o, "vlan_id", json_object_new_int(vlan_id));
}
json_object_object_add(o, "is_wired", json_object_new_boolean(false));
json_object_object_add(o, "ccq", json_object_new_int(s->ccq));
json_object_object_add(o, "idletime", json_object_new_int(0));
json_object_array_add(arr, o);
+3 -1
View File
@@ -72,7 +72,9 @@ int clients_read_wifi(const char *wlan_iface,
struct json_object *clients_build_sta_table(const char *wlan_iface,
const char *radio_band,
int channel,
const char *vap_name);
const char *vap_name,
int vlan_id,
int is_11r);
/* Busca IP en /proc/net/arp dado un MAC. */
int clients_mac_to_ip(const char *mac, char *ip_out, size_t sz);
+42 -6
View File
@@ -360,21 +360,31 @@ static struct json_object *build_vap_table(const uf_model_t *m)
const char *radio = "ng";
const char *bssid = "00:00:00:00:00:00";
const char *vap_id = NULL;
const char *ifname = NULL;
int vlan_id = 0;
int is_11r = 0;
if (json_object_object_get_ex(vap, "essid", &v)) essid = json_object_get_string(v);
if (json_object_object_get_ex(vap, "name", &v)) vap_name = json_object_get_string(v);
if (json_object_object_get_ex(vap, "radio", &v)) radio = json_object_get_string(v);
if (json_object_object_get_ex(vap, "bssid", &v)) bssid = json_object_get_string(v);
if (json_object_object_get_ex(vap, "id", &v)) vap_id = json_object_get_string(v);
if (json_object_object_get_ex(vap, "ifname", &v)) ifname = json_object_get_string(v);
if (json_object_object_get_ex(vap, "vlan_id", &v)) vlan_id = json_object_get_int(v);
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 */
char wlan_iface[32] = "wlan0";
char wlan_iface[32] = "phy0-ap0";
if (ifname && ifname[0])
snprintf(wlan_iface, sizeof(wlan_iface), "%s", ifname);
int channel = 6;
for (int j = 0; j < m->radio_map_len; j++) {
if (strcmp(m->radio_map[j].band, radio) == 0) {
int idx = 0;
sscanf(m->radio_map[j].device, "radio%d", &idx);
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%d", idx);
if (!ifname || !ifname[0])
snprintf(wlan_iface, sizeof(wlan_iface), "phy%d-ap0", idx);
radio_stats_t rs;
if (sysinfo_radio(wlan_iface, &rs) == 0 && rs.channel)
channel = rs.channel;
@@ -390,7 +400,8 @@ static struct json_object *build_vap_table(const uf_model_t *m)
/* Clientes conectados a esta VAP */
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);
int num_sta = json_object_array_length(sta_tbl);
/* Calcular tx_power del radio correspondiente */
@@ -408,6 +419,8 @@ static struct json_object *build_vap_table(const uf_model_t *m)
json_object_new_string(vap_name));
json_object_object_add(o, "radio",
json_object_new_string(radio));
if (vlan_id > 0)
json_object_object_add(o, "vlan_id", json_object_new_int(vlan_id));
json_object_object_add(o, "up",
json_object_new_boolean(iface_st.up));
json_object_object_add(o, "channel",
@@ -448,6 +461,25 @@ static struct json_object *build_vap_table(const uf_model_t *m)
return arr;
}
/* Build the device-level station table UniFi uses for client ownership. */
static struct json_object *collect_sta_table(struct json_object *vap_table)
{
struct json_object *all = json_object_new_array();
int vap_count = json_object_array_length(vap_table);
for (int i = 0; i < vap_count; i++) {
struct json_object *vap = json_object_array_get_idx(vap_table, i);
struct json_object *stations;
if (!json_object_object_get_ex(vap, "sta_table", &stations) ||
!json_object_is_type(stations, json_type_array))
continue;
int count = json_object_array_length(stations);
for (int j = 0; j < count; j++)
json_object_array_add(all, json_object_get(
json_object_array_get_idx(stations, j)));
}
return all;
}
/* ═══════════════════════════════════════════════════════════════════
build_payload — ensamblado completo del JSON inform
═══════════════════════════════════════════════════════════════════ */
@@ -546,8 +578,12 @@ static char *build_payload(const openuf_state_t *st,
build_port_table(root, m);
build_eth_table(root, m);
/* ── VAPs con clientes WiFi (sta_table anidado) ─────────────── */
json_object_object_add(root, "vap_table", build_vap_table(m));
/* Publish both per-VAP and device-level station views. */
struct json_object *vap_table = build_vap_table(m);
struct json_object *sta_table = collect_sta_table(vap_table);
int station_count = json_object_array_length(sta_table);
json_object_object_add(root, "vap_table", vap_table);
json_object_object_add(root, "sta_table", sta_table);
/* ── Vecinos LLDP para topología visual ─────────────────────── */
json_object_object_add(root, "lldp_table", lldp_read_neighbors());
@@ -555,7 +591,7 @@ static char *build_payload(const openuf_state_t *st,
/* Contadores globales */
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(0));
json_object_object_add(root, "num_sta", json_object_new_int(station_count));
const char *s = json_object_to_json_string(root);
+120 -38
View File
@@ -122,6 +122,36 @@ static int valid_object_id(const char *id)
return 1;
}
/* Build one stable 802.11r mobility domain shared by every AP for an SSID. */
static void mobility_domain_for_ssid(const char *ssid, char out[5])
{
unsigned int hash = 2166136261u;
const unsigned char *p = (const unsigned char *)(ssid ? ssid : "");
while (*p) {
hash ^= *p++;
hash *= 16777619u;
}
snprintf(out, 5, "%04x", (hash ^ (hash >> 16)) & 0xffffu);
}
/* Read a UniFi boolean while accepting names used by controller versions. */
static int json_boolean_any(struct json_object *object,
const char *const *keys, size_t key_count)
{
struct json_object *value;
for (size_t i = 0; i < key_count; i++) {
if (!json_object_object_get_ex(object, keys[i], &value))
continue;
if (json_object_is_type(value, json_type_string)) {
const char *text = json_object_get_string(value);
return !strcasecmp(text, "enabled") || !strcasecmp(text, "true") ||
!strcasecmp(text, "on") || !strcmp(text, "1");
}
return json_object_get_boolean(value) ? 1 : 0;
}
return 0;
}
/* Safe UCI section name (maximum 15 characters). */
static void safe_section_name(const char *ssid, char *out, size_t sz)
{
@@ -483,6 +513,7 @@ static int apply_vap(struct uci_context *ctx,
int vap_idx)
{
struct json_object *v;
(void)mac_str;
const char *essid = "";
const char *security = "wpa2psk";
const char *pass = "";
@@ -491,6 +522,19 @@ static int apply_vap(struct uci_context *ctx,
if (json_object_object_get_ex(vap_json, "security", &v)) security = json_object_get_string(v);
if (json_object_object_get_ex(vap_json, "x_passphrase",&v)) pass = json_object_get_string(v);
/* Resolve the final network before creating the VAP; never fail open. */
int vid = 0;
char target_network[32] = "lan";
if (json_object_object_get_ex(vap_json, "vlan_id", &v))
vid = json_object_get_int(v);
if (vid > 0) {
if (ensure_vlan_network(vid) != 0) {
printf("[openuf] Failed to configure VLAN network %d\n", vid);
return -1;
}
snprintf(target_network, sizeof(target_network), "vlan%d", vid);
}
/* Nombre de sección: openuf_<idx>_<ssid_safe> */
char safe[16] = {0};
safe_section_name(essid, safe, sizeof(safe));
@@ -505,7 +549,7 @@ static int apply_vap(struct uci_context *ctx,
UCI_SET(ctx, "wireless", sec_name, "device", device_name);
UCI_SET(ctx, "wireless", sec_name, "mode", "ap");
UCI_SET(ctx, "wireless", sec_name, "ssid", essid);
UCI_SET(ctx, "wireless", sec_name, "network", "lan");
UCI_SET(ctx, "wireless", sec_name, "network", target_network);
UCI_SET(ctx, "wireless", sec_name, "encryption", sec_to_uci(security));
/*
@@ -567,25 +611,18 @@ static int apply_vap(struct uci_context *ctx,
/* ── 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. */
int ft = 0;
if (json_object_object_get_ex(vap_json, "fast_roaming_enabled", &v))
ft = json_object_get_boolean(v) ? 1 : 0;
const char *ft_keys[] = {
"fast_roaming_enabled", "fast_roaming", "ft_enabled", "ieee80211r"
};
int ft = json_boolean_any(vap_json, ft_keys,
sizeof(ft_keys) / sizeof(ft_keys[0]));
if (ft) {
char mdomain[5];
mobility_domain_for_ssid(essid, mdomain);
UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211r", 1);
UCI_SET_INT(ctx, "wireless", sec_name, "ft_over_ds", 1);
UCI_SET_INT(ctx, "wireless", sec_name, "ft_over_ds", 0);
UCI_SET_INT(ctx, "wireless", sec_name, "ft_psk_generate_local", 1);
/* mobility_domain: derivar de MAC del AP (2 bytes) */
char mdomain[8] = {0};
if (mac_str && strlen(mac_str) >= 5) {
/* Usar bytes 0 y 1 de la MAC como dominio */
char b0[3]={mac_str[0],mac_str[1],0};
char b1[3]={mac_str[3],mac_str[4],0};
unsigned int v0=0,v1=0;
sscanf(b0,"%x",&v0); sscanf(b1,"%x",&v1);
snprintf(mdomain, sizeof(mdomain), "%02x%02x", v0, v1);
} else {
strcpy(mdomain, "1234");
}
/* Local key generation avoids external R0KH/R1KH dependencies. */
UCI_SET(ctx, "wireless", sec_name, "mobility_domain", mdomain);
} else {
UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211r", 0);
@@ -609,22 +646,9 @@ static int apply_vap(struct uci_context *ctx,
UCI_SET_INT(ctx, "wireless", sec_name, "ieee80211v", 0);
}
/* ── VLAN ──────────────────────────────────────────────────────
* Si vlan_id ≠ 0, configurar la interfaz con VLAN tagging. */
if (json_object_object_get_ex(vap_json, "vlan_id", &v)) {
int vid = json_object_get_int(v);
if (vid > 0) {
if (ensure_vlan_network(vid) != 0) {
printf("[openuf] Failed to configure VLAN network %d\n", vid);
return -1;
}
/* Record the controller VLAN for telemetry and diagnostics. */
if (vid > 0)
UCI_SET_INT(ctx, "wireless", sec_name, "vlan_id", vid);
/* Establecer network a vlanXXX si existe */
char vlan_net[32];
snprintf(vlan_net, sizeof(vlan_net), "vlan%d", vid);
UCI_SET(ctx, "wireless", sec_name, "network", vlan_net);
}
}
/* Reassert the binding last and reject incomplete UCI sections. */
if (uci_set_required(ctx, pkg, sec_name, "device", device_name) != 0) {
@@ -632,9 +656,14 @@ static int apply_vap(struct uci_context *ctx,
essid, device_name);
return -1;
}
if (uci_set_required(ctx, pkg, sec_name, "network", target_network) != 0) {
printf("[openuf] Refusing unsafe VAP '%s': cannot bind to %s\n",
essid, target_network);
return -1;
}
printf("[openuf] VAP '%s' -> %s device=%s enc=%s ft=%d bs=%d pmf=%d\n",
essid, sec_name, device_name, sec_to_uci(security),
printf("[openuf] VAP '%s' -> %s device=%s network=%s enc=%s ft=%d bs=%d pmf=%d\n",
essid, sec_name, device_name, target_network, sec_to_uci(security),
ft, band_steer, pmf);
return 0;
}
@@ -868,6 +897,20 @@ int wlan_apply_system_cfg(const char *system_cfg,
json_object_object_add(vap, "essid",
json_object_new_string(value));
/* Preserve the WLAN ObjectId used to attach clients in topology. */
const char *id_suffixes[] = { "id", "_id", "wlanconf_id" };
for (size_t id_index = 0;
id_index < sizeof(id_suffixes) / sizeof(id_suffixes[0]);
id_index++) {
snprintf(key, sizeof(key), "aaa.%d.%s", i,
id_suffixes[id_index]);
if (system_cfg_get(system_cfg, key, value, sizeof(value)) &&
valid_object_id(value)) {
json_object_object_add(vap, "id", json_object_new_string(value));
break;
}
}
snprintf(key, sizeof(key), "aaa.%d.status", i);
if (system_cfg_get(system_cfg, key, value, sizeof(value)) &&
strcmp(value, "enabled")) {
@@ -950,6 +993,40 @@ int wlan_apply_system_cfg(const char *system_cfg,
También intentamos leer el BSSID real de la interfaz wlan
desde /sys/class/net/<iface>/address.
*/
/* Resolve a configured VAP to the live interface reported by nl80211. */
static int find_runtime_vap(int phy_index, const char *ssid,
char *out, size_t out_size)
{
FILE *pipe = popen("iw dev 2>/dev/null", "r");
if (!pipe) return -1;
int phy = -1;
char candidate[32] = "";
char line[256];
while (fgets(line, sizeof(line), pipe)) {
int parsed_phy;
char value[128];
if (sscanf(line, "phy#%d", &parsed_phy) == 1) {
phy = parsed_phy;
candidate[0] = '\0';
continue;
}
if (sscanf(line, " Interface %31s", value) == 1) {
snprintf(candidate, sizeof(candidate), "%s", value);
continue;
}
if (phy == phy_index && candidate[0] &&
sscanf(line, " ssid %127[^\n]", value) == 1 &&
!strcmp(value, ssid)) {
snprintf(out, out_size, "%s", candidate);
pclose(pipe);
return 0;
}
}
pclose(pipe);
return -1;
}
struct json_object *wlan_get_vap_table(const uf_model_t *model)
{
struct json_object *arr = json_object_new_array();
@@ -981,6 +1058,7 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
const char *w11 = UCI_GET("ieee80211w");
const char *hidden = UCI_GET("hidden");
const char *vap_id = UCI_GET("openuf_vap_id");
const char *vlan = UCI_GET("vlan_id");
if (!ssid) ssid = "";
if (!device) device = "radio0";
@@ -994,11 +1072,12 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
}
}
/* Nombre de la interfaz wlan (wlan0 para radio0, etc.) */
char wlan_iface[32] = "wlan0";
/* Resolve the actual netifd interface (for example phy1-ap0). */
char wlan_iface[32];
int ridx = 0;
sscanf(device, "radio%d", &ridx);
snprintf(wlan_iface, sizeof(wlan_iface), "wlan%d", ridx);
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 */
char bssid[32] = "00:00:00:00:00:00";
@@ -1027,7 +1106,8 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
struct json_object *o = json_object_new_object();
json_object_object_add(o, "essid", json_object_new_string(ssid));
json_object_object_add(o, "bssid", json_object_new_string(bssid));
json_object_object_add(o, "name", json_object_new_string(sec->e.name));
json_object_object_add(o, "name", json_object_new_string(wlan_iface));
json_object_object_add(o, "ifname", json_object_new_string(wlan_iface));
json_object_object_add(o, "radio", json_object_new_string(radio_band));
json_object_object_add(o, "security", json_object_new_string(sec_to_unifi(enc)));
json_object_object_add(o, "up", json_object_new_boolean(up));
@@ -1036,6 +1116,8 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model)
json_object_object_add(o, "band_steering", json_object_new_boolean(bs_on));
json_object_object_add(o, "pmf_mode", json_object_new_string(pmf));
json_object_object_add(o, "num_sta", json_object_new_int(0));
if (vlan && atoi(vlan) > 0)
json_object_object_add(o, "vlan_id", json_object_new_int(atoi(vlan)));
if (valid_object_id(vap_id))
json_object_object_add(o, "id", json_object_new_string(vap_id));
json_object_array_add(arr, o);