KodaandKoda 308803053c
Generate and publish developer Wiki / validate-wiki (push) Successful in 21s
Build and publish release / metadata (push) Successful in 45s
Generate and publish developer Wiki / publish-wiki (push) Successful in 12s
Build and publish release / create-release (push) Successful in 55s
Build and publish release / build (push) Failing after 23m29s
Build and publish release / publish-release (push) Skipped
fixing release pipeline (#40)
Reviewed-on: #40
Co-authored-by: Koda YeenBean <n122330@gmail.com>
2026-07-20 00:03:28 +01:00
2026-07-14 17:37:18 +01:00
2026-07-20 00:03:28 +01:00
2026-07-19 22:34:26 +01:00
2026-07-20 00:03:28 +01:00
2026-07-10 12:42:06 +01:00

openUF

Daemon that makes an OpenWrt router appear as a UniFi AP to UniFi Network controllers.

Implemented Features

Feature Description Implementation
L2 Discovery UDP broadcast + multicast every 10s announce.c → port 10001
Adoption AES-128-CBC/GCM handshake with the controller inform/response.cinform_handle_response()
Remote reboot Reboots OpenWrt when requested by the controller inform/response.cinform_handle_response()
Firmware spoofing Persists and reports the target version requested by an upgrade inform/response.cinform_handle_response()
WiFi Config Creates WiFi networks from the controller via UCI wlan/provision.cwlan_apply_config()
Band Steering 802.11k/v Neighbor Reports + BSS Transition wlan/provision.c and wlan/uci.c
Fast Roaming 802.11r FT with a stable SSID mobility domain wlan/provision.capply_vap()
WPA3 / PMF SAE, SAE-mixed, 802.11w 0/1/2 wlan/common.c and wlan/provision.c
WiFi Clients MAC, signal, bitrate, bytes per VAP clients.ciw station dump
Wired Clients MACs from bridge FDB clients.cbridge fdb
CPU / RAM Real-time usage sysinfo.c/proc/stat + /proc/meminfo
Interfaces Speed, duplex, rx/tx counters sysinfo.c/proc/net/dev
Channel / RF Channel utilization, noise, tx_power sysinfo.ciw survey dump
LLDP Send Custom frames via AF_PACKET raw socket lldp.clldp_send_frame()
LLDP Read Neighbors for UniFi topology lldp.clldpctl -f json

Building in the dev container

# From the OpenWrt build root
cd /home/openwrt/openwrt

# Build only this package
make package/OpenUniFi/compile

# For a clean package rebuild
make package/OpenUniFi/clean
make package/OpenUniFi/compile

Makefile.standalone is an alternative for compiling directly on an OpenWrt device that has the development dependencies installed:

make -f Makefile.standalone

Contributors and AI agents should read AGENTS.md for the concise architecture map, invariants, and validation checklist.

Developer documentation and Gitea Wiki

The publish-ready pages live in the separate Koda/openunifi.wiki repository. They include a developer guide, a generated Mermaid function-call graph, and runtime flow charts. Check out that repository, then refresh it after source or architecture changes:

./scripts/docs/generate-wiki.py --output /path/to/openunifi.wiki

Use the same tool as a linter to reject stale pages, oversized C modules, missing documented entry points, or known non-English comment fragments:

./scripts/docs/generate-wiki.py --check --output /path/to/openunifi.wiki

The generator uses only the Python 3 standard library, and the publisher uses POSIX shell utilities and Git. Neither downloads or executes an x86-only helper, so both run natively on ARM and x86 build agents.

Publishing is an explicit authenticated step: ./scripts/docs/publish-wiki.sh. The script derives the lowercase .wiki.git repository URL from origin, checks it out before generating, and accepts an explicit URL override. No credentials are stored in this repository. The documentation workflow clones the Wiki Git endpoint directly because it is not a normal API repository. Its publishing job requests Gitea's wiki: write permission for GITEA_TOKEN and passes the token through a transient HTTP authentication header. Repository Actions settings must allow Wiki write access because Gitea clamps requested job permissions to the configured maximum.

Automated releases

Every push to main runs .gitea/workflows/release.yaml. It builds with pinned OpenWrt SDKs and publishes openUniFi-<architecture>-<version>.apk for x64, x86, arm64, arm32, and mpc85xx. The workflow uses Gitea's built-in GITEA_TOKEN; repository Actions settings must allow it write access to releases. A failed target leaves the release as a draft rather than publishing an incomplete set.

Targets and the pinned OpenWrt release live in .gitea/release-targets.json. Add an object with a unique architecture label and a valid OpenWrt target/subtarget pair to extend the build list; no workflow change is required. The generic ARM entries select ARMv7 and ARMv8 ABIs; use a device-specific OpenWrt target when necessary.

OpenWrt publishes its release SDKs with x86-64 host tools, even when the target is ARM or PowerPC. On an ARM build agent, the workflow installs qemu-user-static and the build script runs the SDK host tools explicitly through qemu-x86_64-static; privileged binfmt_misc registration is not required. Native x86-64 agents skip emulation and remain faster.

Releases use the next available MAJOR.MINOR.PATCH tag. MAJOR and MINOR live in the release config; the workflow finds the highest existing vMAJOR.MINOR.PATCH tag and increments its patch, starting at 0 when a new major or minor line is introduced. For example, changing the config from 0.4 to 0.5 makes the next release 0.5.0. Release runs are serialized so two merges cannot select the same patch version.

Alternatives are Conventional Commits with a semantic-release tool (best when not every merge must release), or a manually maintained VERSION file bumped in every pull request (simple, but a forgotten bump blocks the release). Change major for incompatible changes and minor for compatible features.

Changing Compiler Settings

# OpenWRT build directory
cd /home/openwrt/openwrt

# Change settings
make menuconfig

# export settings
./scripts/diffconfig.sh

# Paste settings into .devcontainer/openwrt.config file in the code
# Then rebuild the dev container by pressing F1 in VScode
# and typing rebuild and selecting rebuild dev container

Configuration

On the access point, run the following to install the package:

# Install dependencies
# replace wpad-basic-mbedtls with wpad-mbedtls
apk del wpad-basic-mbedtls && apk add wpad-mbedtls && /etc/init.d/network restart

# Remove old installation
apk -r del openuf

# Install new version
apk add openuf-0.4.0-r3.apk --allow-untrusted

# Configure
vi /etc/openuf/openuf.conf

Example Configuration

controller_ip   =               # Empty: use IPv4 default gateway
lan_if          = br-lan        # Default Lan interface Name
ufmodel         = u6-inwall     # emulated model
inform_interval = 10            # inform interval
enable_announce = 1
enable_inform   = 1             # Enable adoption and telemetry requests
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)

Set controller_ip to a controller IPv4 address or hostname to override autodetection. The automatic mode uses the IPv4 default-route gateway for initial adoption; after adoption, the controller-provided inform URL is kept.

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

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

S
Description
No description provided
Readme Apache-2.0
765 KiB
Languages
C 87.9%
Python 7.5%
Shell 3.6%
Makefile 0.6%
Dockerfile 0.4%