From 93e6ac671666020de8317713f79ade80a2935807 Mon Sep 17 00:00:00 2001 From: Koda YeenBean Date: Mon, 13 Jul 2026 21:16:02 +0100 Subject: [PATCH] added automatic latest firmware spoofing (#16) Reviewed-on: https://git.ascheu.de/Koda/OpenUniFi/pulls/16 --- AGENTS.md | 25 +++++++++++++++++++++++++ README.md | 5 +++++ src/inform.c | 29 ++++++++++++++++++++++++++++- src/inform.h | 2 +- src/state.c | 2 ++ src/state.h | 1 + 6 files changed, 62 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index da7763e..74962ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,3 +89,28 @@ behavior that still needs manual testing. state in the handoff. - Do not commit generated binaries, OpenWrt build output, runtime state, logs, or captured protocol payloads. + +## Environment pitfalls and shared discoveries + +- In this workspace, the Codex `apply_patch` helper is currently unusable. It + fails with `bwrap: No permissions to create a new namespace`, including when + invoked from an approved command outside the normal sandbox. Do not spend time + retrying it or tracing its symlink after seeing this known error. +- For repository edits, use `git apply --recount` with a focused unified diff as + the fallback. `--recount` avoids failures caused by manually miscounted hunk + lengths. Review the resulting `git diff` and run `git diff --check` afterward. + Preserve unrelated worktree changes exactly as with any other editing method. +- Ordinary read-only commands may fail with the same `bwrap` namespace error. + When the command is safe and required, rerun it using the environment's normal + escalation/approval mechanism instead of investigating the namespace setup. + +Agents must preserve useful environment knowledge for later agents. When a new +pitfall or workaround is verified to be deterministic or repeatedly encountered, +add a short entry to this section before handoff. Keep these entries actionable +and specific: include the recognizable failure, the confirmed workaround, and +any validation required afterward. + +Do not record transient command failures, guesses, lengthy logs, credentials, +auth keys, host secrets, or details that only apply to a single task. If a +discovery could weaken safety controls or requires broader permissions, report +it to the user instead of documenting a bypass. diff --git a/README.md b/README.md index a755657..335edef 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Daemon that makes an OpenWrt router appear as a UniFi AP to UniFi Network contro | --- | --- | --- | | **L2 Discovery** | UDP broadcast + multicast every 10s | `announce.c` → port 10001 | | **Adoption** | AES-128-CBC handshake with the controller | `inform.c` → `handle_response()` | +| **Firmware spoofing** | Persists and reports the target version requested by an upgrade | `inform.c` → `handle_response()` | | **WiFi Config** | Creates WiFi networks from the controller via UCI | `wlan.c` → `wlan_apply_config()` | | **Band Steering** | 802.11k/v Neighbor Reports + BSS Transition | `wlan.c` → `apply_vap()` | | **Fast Roaming** | 802.11r FT with mobility_domain derived from MAC | `wlan.c` → `apply_vap()` | @@ -99,6 +100,10 @@ Daemon settings live in `/etc/openuf/openuf.conf`. Adoption and controller state are persisted separately in `/etc/openuf/state.json`; do not copy that file between devices because it contains the device authentication key. +Because UniFi firmware cannot run on the OpenWrt host, an upgrade request does +not download or install its image. Instead, openUF saves the requested firmware +version in `state.json` and reports it in subsequent inform packets. + --- ## Glossary diff --git a/src/inform.c b/src/inform.c index 1365d72..850bffc 100644 --- a/src/inform.c +++ b/src/inform.c @@ -575,7 +575,12 @@ static char *build_payload(const openuf_state_t *st, } char fw_version[64]; - snprintf(fw_version, sizeof(fw_version), "%s%s", m->fw_pre, m->fw_ver); + if (st->firmware_version[0]) + snprintf(fw_version, sizeof(fw_version), "%s", + st->firmware_version); + else + snprintf(fw_version, sizeof(fw_version), "%s%s", + m->fw_pre, m->fw_ver); char inform_url_buf[256]; if (st->inform_url[0]) @@ -812,6 +817,28 @@ static void handle_response(openuf_state_t *st, LOG("Handling response type: %s", type); + /* An OpenWrt host cannot install UniFi firmware. Acknowledge the + * controller's request by reporting its target version from now on. */ + if (!strcmp(type, "upgrade")) { + if (json_object_object_get_ex(resp, "version", &v) && + json_object_is_type(v, json_type_string)) { + const char *version = json_object_get_string(v); + size_t len = strlen(version); + if (len > 0 && len < sizeof(st->firmware_version)) { + snprintf(st->firmware_version, + sizeof(st->firmware_version), "%s", version); + state_save(st); + LOG("Firmware upgrade spoofed; now reporting version=%s", + st->firmware_version); + strcpy(action_out, "upgrade-spoofed"); + return; + } + } + LOG("Ignoring upgrade response without a valid version"); + strcpy(action_out, "upgrade-invalid"); + return; + } + /* ── noop ────────────────────────────────────────────────────── */ if (!strcmp(type, "noop")) { strcpy(action_out, "noop"); diff --git a/src/inform.h b/src/inform.h index 04a8aa0..2d68501 100644 --- a/src/inform.h +++ b/src/inform.h @@ -28,7 +28,7 @@ void inform_set_debug_level(int level); /* Send one inform cycle. - * Updates *st in place (adopted flag, auth key, inform_url, cfgversion). + * Updates *st in place (adoption, controller config, and spoofed firmware). * Returns 0 on success, -1 on error (sets err_out[0..127]). */ int inform_send(openuf_state_t *st, const uf_model_t *model, diff --git a/src/state.c b/src/state.c index 25d4ca2..63948c9 100644 --- a/src/state.c +++ b/src/state.c @@ -68,6 +68,7 @@ void state_load(openuf_state_t *st) LOAD_STR (authkey, "authkey"); LOAD_STR (inform_url, "inform_url"); LOAD_STR (cfgversion, "cfgversion"); + LOAD_STR (firmware_version, "firmware_version"); LOAD_BOOL(config_applied, "config_applied"); LOAD_INT (config_schema, "config_schema"); LOAD_BOOL(use_aes_gcm, "use_aes_gcm"); @@ -112,6 +113,7 @@ int state_save(const openuf_state_t *st) json_object_object_add(root, "authkey", json_object_new_string(st->authkey)); json_object_object_add(root, "inform_url", json_object_new_string(st->inform_url)); json_object_object_add(root, "cfgversion", json_object_new_string(st->cfgversion)); + json_object_object_add(root, "firmware_version", json_object_new_string(st->firmware_version)); json_object_object_add(root, "config_applied", json_object_new_boolean(st->config_applied)); json_object_object_add(root, "config_schema", json_object_new_int(st->config_schema)); json_object_object_add(root, "use_aes_gcm", json_object_new_boolean(st->use_aes_gcm)); diff --git a/src/state.h b/src/state.h index a8fa5e6..d9a9dd2 100644 --- a/src/state.h +++ b/src/state.h @@ -10,6 +10,7 @@ typedef struct { char authkey[64]; char inform_url[256]; char cfgversion[32]; + char firmware_version[64]; bool config_applied; int config_schema; bool use_aes_gcm;