From a06ef4e9f6e32da52297ddbcda70dfe7320213b9 Mon Sep 17 00:00:00 2001 From: Koda YeenBean Date: Mon, 13 Jul 2026 19:29:35 +0000 Subject: [PATCH] Fixed Topology Graph Mappings Added advanced debug levels --- README.md | 3 ++ files/openuf.conf | 3 ++ src/config.c | 3 ++ src/config.h | 1 + src/inform.c | 62 ++++++++++++++++++++++++++- src/inform.h | 3 ++ src/main.c | 2 + src/wlan.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 183 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cf506c..d6f8038 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ ufmodel = u6-inwall # emulated model inform_interval = 10 # inform interval enable_announce = 1 enable_inform = 1 # Enable logging to /var/log/openuf.log if set to 1 +enable_logging = 1 # Write diagnostics to /var/log/openuf.log +protocol_debug_level = 0 # 0=off, 1=safe response details, + # 2=full decrypted JSON (contains secrets) ``` diff --git a/files/openuf.conf b/files/openuf.conf index be0d3de..3665479 100644 --- a/files/openuf.conf +++ b/files/openuf.conf @@ -7,6 +7,8 @@ # enable_announce: 1=enable UDP discovery (port 10001) # enable_inform: 1=enable HTTP inform requests (adoption and telemetry) # enable_logging: 1=enable logging to /var/log/openuf.log (default: off) +# protocol_debug_level: 0=off, 1=safe response structure, +# 2=full decrypted response; may expose credentials controller_ip = 10.10.10.1 lan_if = br-lan @@ -15,3 +17,4 @@ inform_interval = 10 enable_announce = 1 enable_inform = 1 enable_logging = 0 +protocol_debug_level = 0 diff --git a/src/config.c b/src/config.c index 47a0508..42c0ab9 100644 --- a/src/config.c +++ b/src/config.c @@ -14,6 +14,7 @@ void config_load(openuf_config_t *cfg) cfg->enable_inform = 1; /* File logging is opt-in to avoid flash wear and unbounded log growth. */ cfg->enable_logging = 0; + cfg->protocol_debug_level = 0; FILE *f = fopen(OPENUF_CONF_FILE, "r"); if (!f) return; @@ -35,6 +36,8 @@ void config_load(openuf_config_t *cfg) else if (!strcmp(key, "enable_announce")) cfg->enable_announce = atoi(val); else if (!strcmp(key, "enable_inform")) cfg->enable_inform = atoi(val); else if (!strcmp(key, "enable_logging")) cfg->enable_logging = atoi(val); + else if (!strcmp(key, "protocol_debug_level")) + cfg->protocol_debug_level = atoi(val); } fclose(f); } diff --git a/src/config.h b/src/config.h index e81fe49..f474fd4 100644 --- a/src/config.h +++ b/src/config.h @@ -35,6 +35,7 @@ typedef struct { int enable_announce; int enable_inform; int enable_logging; + int protocol_debug_level; } openuf_config_t; /* Parse /etc/openuf/openuf.conf (simple key=value). diff --git a/src/inform.c b/src/inform.c index 6e6d435..1365d72 100644 --- a/src/inform.c +++ b/src/inform.c @@ -94,6 +94,61 @@ static int valid_authkey(const char *key) return 1; } +static int protocol_debug_level; + +void inform_set_debug_level(int level) +{ + protocol_debug_level = level < 0 ? 0 : level > 2 ? 2 : level; +} + +static int debug_system_cfg_key(const char *key) +{ + if (!key || (strncmp(key, "aaa.", 4) && + strncmp(key, "wireless.", 9))) + return 0; + return strstr(key, ".ssid") || strstr(key, ".id") || + strstr(key, ".vap_ind") || strstr(key, ".parent"); +} + +static void debug_log_controller_response(struct json_object *response, + const char *raw_json) +{ + if (protocol_debug_level <= 0 || !response) + return; + + LOG("Protocol debug: decrypted controller response fields follow"); + json_object_object_foreach(response, key, value) { + LOG("Protocol field: %s type=%s", key, + json_type_to_name(json_object_get_type(value))); + } + + struct json_object *system_cfg_object; + if (json_object_object_get_ex(response, "system_cfg", + &system_cfg_object)) { + const char *system_cfg = json_object_get_string(system_cfg_object); + char *copy = system_cfg ? strdup(system_cfg) : NULL; + if (copy) { + char *save = NULL; + for (char *line = strtok_r(copy, "\n", &save); + line; line = strtok_r(NULL, "\n", &save)) { + line[strcspn(line, "\r")] = '\0'; + char *equals = strchr(line, '='); + if (!equals) + continue; + *equals = '\0'; + if (debug_system_cfg_key(line)) + LOG("Protocol system_cfg: %s=%s", line, equals + 1); + } + free(copy); + } + } + + if (protocol_debug_level >= 2) { + LOG("WARNING: full decrypted response may contain credentials"); + LOG("Protocol response JSON: %s", raw_json ? raw_json : ""); + } +} + /* ═══════════════════════════════════════════════════════════════════ sys_stats — CPU and memory of the system ═══════════════════════════════════════════════════════════════════ @@ -465,6 +520,9 @@ static struct json_object *build_vap_table(const uf_model_t *m) /* Only controller-issued ObjectIds are valid in this field. */ if (vap_id) json_object_object_add(o, "id", json_object_new_string(vap_id)); + if (vap_id) + json_object_object_add(o, "wlanconf_id", + json_object_new_string(vap_id)); json_object_object_add(o, "usage", json_object_new_string("user")); json_object_object_add(o, "ccq", @@ -1070,13 +1128,15 @@ int inform_send(openuf_state_t *st, LOG("Parsed response JSON, length=%zu", strlen(resp_json)); struct json_object *resp_obj = json_tokener_parse(resp_json); - free(resp_json); if (!resp_obj) { LOG("Failed to parse JSON"); snprintf(err_out, 127, "JSON parse failed"); + free(resp_json); return -1; } + debug_log_controller_response(resp_obj, resp_json); + free(resp_json); struct json_object *response_type; if (json_object_object_get_ex(resp_obj, "_type", &response_type)) LOG("Parsed response type: %s", diff --git a/src/inform.h b/src/inform.h index 117dff7..04a8aa0 100644 --- a/src/inform.h +++ b/src/inform.h @@ -24,6 +24,9 @@ #define INFORM_FLAG_ENCRYPTED 0x0001 #define INFORM_FLAG_GCM 0x0008 +/* 0=off, 1=safe response structure, 2=full decrypted response (secrets). */ +void inform_set_debug_level(int level); + /* Send one inform cycle. * Updates *st in place (adopted flag, auth key, inform_url, cfgversion). * Returns 0 on success, -1 on error (sets err_out[0..127]). */ diff --git a/src/main.c b/src/main.c index 8704e1b..afaa020 100644 --- a/src/main.c +++ b/src/main.c @@ -80,6 +80,8 @@ int main(int argc, char *argv[]) LOG("Logging enabled"); } } + inform_set_debug_level(cfg.protocol_debug_level); + LOG("Protocol debug level=%d", cfg.protocol_debug_level); #endif const uf_model_t *model = ufmodel_find(cfg.ufmodel); diff --git a/src/wlan.c b/src/wlan.c index 4d8cedd..5ae3a0f 100644 --- a/src/wlan.c +++ b/src/wlan.c @@ -81,6 +81,7 @@ #include "wlan.h" #include "ufmodel.h" +#include "crypto.h" #define MAX_RESOLVED_RADIOS 8 @@ -263,6 +264,102 @@ static int valid_object_id(const char *id) return 0; return 1; } +/* + * UniFi uses a 24-character hexadecimal MongoDB ObjectId to associate VAP + * telemetry and stations with a WLAN. Older legacy system_cfg payloads do + * not include that ID, so allocate a local one and keep it stable in UCI. + * All radio instances of the same WLAN must report the same ID. + */ +static int ensure_local_vap_ids(struct json_object *vaps) +{ + if (!vaps || !json_object_is_type(vaps, json_type_array)) + return 0; + + struct uci_context *ctx = uci_alloc_context(); + struct uci_package *pkg = NULL; + if (ctx) + uci_load(ctx, "wireless", &pkg); + + int count = json_object_array_length(vaps); + for (int i = 0; i < count; i++) { + struct json_object *vap = json_object_array_get_idx(vaps, i); + struct json_object *value; + if (!vap) + continue; + + const char *id = NULL; + const char *id_keys[] = { "_id", "id", "wlanconf_id" }; + for (size_t n = 0; n < sizeof(id_keys) / sizeof(id_keys[0]); n++) { + if (json_object_object_get_ex(vap, id_keys[n], &value)) { + const char *candidate = json_object_get_string(value); + if (valid_object_id(candidate)) { + id = candidate; + break; + } + } + } + if (id) + continue; + + const char *ssid = ""; + if (json_object_object_get_ex(vap, "essid", &value)) + ssid = json_object_get_string(value); + + /* Reuse an ID already assigned to this WLAN in the same payload. */ + for (int j = 0; j < i && !id; j++) { + struct json_object *previous = json_object_array_get_idx(vaps, j); + struct json_object *previous_ssid; + struct json_object *previous_id; + if (previous && + json_object_object_get_ex(previous, "essid", &previous_ssid) && + !strcmp(json_object_get_string(previous_ssid), ssid) && + json_object_object_get_ex(previous, "id", &previous_id) && + valid_object_id(json_object_get_string(previous_id))) + id = json_object_get_string(previous_id); + } + + /* Reuse the ID committed by an earlier provisioning cycle. */ + if (!id && pkg) { + struct uci_element *element; + uci_foreach_element(&pkg->sections, element) { + struct uci_section *section = uci_to_section(element); + const char *stored_ssid; + const char *stored_id; + if (strcmp(section->type, "wifi-iface") || + strncmp(section->e.name, "openuf_", 7)) + continue; + stored_ssid = uci_lookup_option_string(ctx, section, "ssid"); + stored_id = uci_lookup_option_string(ctx, section, + "openuf_vap_id"); + if (stored_ssid && !strcmp(stored_ssid, ssid) && + valid_object_id(stored_id)) { + id = stored_id; + break; + } + } + } + + char generated[25]; + if (!id) { + if (crypto_random_hex((unsigned char *)generated, 12) != 0) { + printf("[openuf] Failed to generate a VAP ID for '%s'\n", ssid); + if (pkg) uci_unload(ctx, pkg); + if (ctx) uci_free_context(ctx); + return -1; + } + id = generated; + printf("[openuf] Generated persistent VAP ID %s for '%s'\n", + id, ssid); + } + + json_object_object_add(vap, "id", json_object_new_string(id)); + json_object_object_add(vap, "wlanconf_id", json_object_new_string(id)); + } + + if (pkg) uci_unload(ctx, pkg); + if (ctx) uci_free_context(ctx); + return 0; +} /* 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]) @@ -970,6 +1067,9 @@ int wlan_apply_config(struct json_object *config_json, struct json_object *rt_arr = NULL, *vt_arr = NULL, *v; json_object_object_get_ex(config_json, "radio_table", &rt_arr); json_object_object_get_ex(config_json, "vap_table", &vt_arr); + /* Fill IDs omitted by legacy provisioning before old UCI VAPs are removed. */ + if (ensure_local_vap_ids(vt_arr) != 0) + return -1; /* Get the AP's MAC for mobility_domain */ char mac_str[32] = "00:00:00:00:00:00"; @@ -1224,6 +1324,8 @@ static int system_cfg_get(const char *cfg, const char *key, if (line_len > key_len && !strncmp(line, key, key_len) && line[key_len] == '=') { size_t value_len = line_len - key_len - 1; + while (value_len && line[key_len + value_len] == '\r') + value_len--; if (value_len >= out_size) value_len = out_size - 1; memcpy(out, line + key_len + 1, value_len); out[value_len] = '\0'; @@ -1291,6 +1393,8 @@ int wlan_apply_system_cfg(const char *system_cfg, 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)); + json_object_object_add(vap, "wlanconf_id", + json_object_new_string(value)); break; } } @@ -1541,6 +1645,9 @@ struct json_object *wlan_get_vap_table(const uf_model_t *model) 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)); + if (valid_object_id(vap_id)) + json_object_object_add(o, "wlanconf_id", + json_object_new_string(vap_id)); json_object_array_add(arr, o); #undef UCI_GET } -- 2.54.0