Fixed Topology Graph Mappings #14
@@ -78,6 +78,9 @@ ufmodel = u6-inwall # emulated model
|
|||||||
inform_interval = 10 # inform interval
|
inform_interval = 10 # inform interval
|
||||||
enable_announce = 1
|
enable_announce = 1
|
||||||
enable_inform = 1 # Enable logging to /var/log/openuf.log if set to 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)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
# enable_announce: 1=enable UDP discovery (port 10001)
|
# enable_announce: 1=enable UDP discovery (port 10001)
|
||||||
# enable_inform: 1=enable HTTP inform requests (adoption and telemetry)
|
# enable_inform: 1=enable HTTP inform requests (adoption and telemetry)
|
||||||
# enable_logging: 1=enable logging to /var/log/openuf.log (default: off)
|
# 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
|
controller_ip = 10.10.10.1
|
||||||
lan_if = br-lan
|
lan_if = br-lan
|
||||||
@@ -15,3 +17,4 @@ inform_interval = 10
|
|||||||
enable_announce = 1
|
enable_announce = 1
|
||||||
enable_inform = 1
|
enable_inform = 1
|
||||||
enable_logging = 0
|
enable_logging = 0
|
||||||
|
protocol_debug_level = 0
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ void config_load(openuf_config_t *cfg)
|
|||||||
cfg->enable_inform = 1;
|
cfg->enable_inform = 1;
|
||||||
/* File logging is opt-in to avoid flash wear and unbounded log growth. */
|
/* File logging is opt-in to avoid flash wear and unbounded log growth. */
|
||||||
cfg->enable_logging = 0;
|
cfg->enable_logging = 0;
|
||||||
|
cfg->protocol_debug_level = 0;
|
||||||
|
|
||||||
FILE *f = fopen(OPENUF_CONF_FILE, "r");
|
FILE *f = fopen(OPENUF_CONF_FILE, "r");
|
||||||
if (!f) return;
|
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_announce")) cfg->enable_announce = atoi(val);
|
||||||
else if (!strcmp(key, "enable_inform")) cfg->enable_inform = 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, "enable_logging")) cfg->enable_logging = atoi(val);
|
||||||
|
else if (!strcmp(key, "protocol_debug_level"))
|
||||||
|
cfg->protocol_debug_level = atoi(val);
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ typedef struct {
|
|||||||
int enable_announce;
|
int enable_announce;
|
||||||
int enable_inform;
|
int enable_inform;
|
||||||
int enable_logging;
|
int enable_logging;
|
||||||
|
int protocol_debug_level;
|
||||||
} openuf_config_t;
|
} openuf_config_t;
|
||||||
|
|
||||||
/* Parse /etc/openuf/openuf.conf (simple key=value).
|
/* Parse /etc/openuf/openuf.conf (simple key=value).
|
||||||
|
|||||||
+61
-1
@@ -94,6 +94,61 @@ static int valid_authkey(const char *key)
|
|||||||
return 1;
|
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
|
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. */
|
/* Only controller-issued ObjectIds are valid in this field. */
|
||||||
if (vap_id)
|
if (vap_id)
|
||||||
json_object_object_add(o, "id", json_object_new_string(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_object_add(o, "usage",
|
||||||
json_object_new_string("user"));
|
json_object_new_string("user"));
|
||||||
json_object_object_add(o, "ccq",
|
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));
|
LOG("Parsed response JSON, length=%zu", strlen(resp_json));
|
||||||
|
|
||||||
struct json_object *resp_obj = json_tokener_parse(resp_json);
|
struct json_object *resp_obj = json_tokener_parse(resp_json);
|
||||||
free(resp_json);
|
|
||||||
if (!resp_obj) {
|
if (!resp_obj) {
|
||||||
LOG("Failed to parse JSON");
|
LOG("Failed to parse JSON");
|
||||||
snprintf(err_out, 127, "JSON parse failed");
|
snprintf(err_out, 127, "JSON parse failed");
|
||||||
|
free(resp_json);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug_log_controller_response(resp_obj, resp_json);
|
||||||
|
free(resp_json);
|
||||||
struct json_object *response_type;
|
struct json_object *response_type;
|
||||||
if (json_object_object_get_ex(resp_obj, "_type", &response_type))
|
if (json_object_object_get_ex(resp_obj, "_type", &response_type))
|
||||||
LOG("Parsed response type: %s",
|
LOG("Parsed response type: %s",
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
#define INFORM_FLAG_ENCRYPTED 0x0001
|
#define INFORM_FLAG_ENCRYPTED 0x0001
|
||||||
#define INFORM_FLAG_GCM 0x0008
|
#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.
|
/* Send one inform cycle.
|
||||||
* Updates *st in place (adopted flag, auth key, inform_url, cfgversion).
|
* Updates *st in place (adopted flag, auth key, inform_url, cfgversion).
|
||||||
* Returns 0 on success, -1 on error (sets err_out[0..127]). */
|
* Returns 0 on success, -1 on error (sets err_out[0..127]). */
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ int main(int argc, char *argv[])
|
|||||||
LOG("Logging enabled");
|
LOG("Logging enabled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inform_set_debug_level(cfg.protocol_debug_level);
|
||||||
|
LOG("Protocol debug level=%d", cfg.protocol_debug_level);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const uf_model_t *model = ufmodel_find(cfg.ufmodel);
|
const uf_model_t *model = ufmodel_find(cfg.ufmodel);
|
||||||
|
|||||||
+107
@@ -81,6 +81,7 @@
|
|||||||
|
|
||||||
#include "wlan.h"
|
#include "wlan.h"
|
||||||
#include "ufmodel.h"
|
#include "ufmodel.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
#define MAX_RESOLVED_RADIOS 8
|
#define MAX_RESOLVED_RADIOS 8
|
||||||
|
|
||||||
@@ -263,6 +264,102 @@ static int valid_object_id(const char *id)
|
|||||||
return 0;
|
return 0;
|
||||||
return 1;
|
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. */
|
/* 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])
|
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;
|
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, "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);
|
||||||
|
/* 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 */
|
/* 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";
|
||||||
@@ -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) &&
|
if (line_len > key_len && !strncmp(line, key, key_len) &&
|
||||||
line[key_len] == '=') {
|
line[key_len] == '=') {
|
||||||
size_t value_len = line_len - key_len - 1;
|
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;
|
if (value_len >= out_size) value_len = out_size - 1;
|
||||||
memcpy(out, line + key_len + 1, value_len);
|
memcpy(out, line + key_len + 1, value_len);
|
||||||
out[value_len] = '\0';
|
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)) &&
|
if (system_cfg_get(system_cfg, key, value, sizeof(value)) &&
|
||||||
valid_object_id(value)) {
|
valid_object_id(value)) {
|
||||||
json_object_object_add(vap, "id", json_object_new_string(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;
|
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)));
|
json_object_object_add(o, "vlan_id", json_object_new_int(atoi(vlan)));
|
||||||
if (valid_object_id(vap_id))
|
if (valid_object_id(vap_id))
|
||||||
json_object_object_add(o, "id", json_object_new_string(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);
|
json_object_array_add(arr, o);
|
||||||
#undef UCI_GET
|
#undef UCI_GET
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user