VilaNet OpenWrt
Official VilaVPN router build

VilaVPN for every device
on your network.

vilanet for OpenWrt is the router-class VilaVPN client. Install one IPK, log in once, and every device on your LAN — phones, TVs, IoT, consoles, laptops — exits through VilaVPN with zero per-device setup. Managed from LuCI or UCI.

2
IPK packages
≥24.10
OpenWrt release
11
CLI verbs
AES-256
GCM envelope
What is VilaNet for OpenWrt

Two IPKs. One router. Every device covered.

VilaNet ships to OpenWrt as a pair of IPK packages — a vilanet-core service binary plus a luci-app-vilanet web UI. Install both with opkg, log in once, and every device on your LAN exits through VilaVPN. No per-device VPN apps, no profile installs. Routing, DNS leak protection, and a kill-switch are managed in UCI and visible in LuCI.

AES-256-GCM credentials envelope

Password is sealed with an AES-256-GCM envelope keyed off a per-device 32-byte secret in /etc/vilanet/device.secret. No plaintext password on flash; legacy plaintext is migrated transparently on first read.

Embedded sing-box 1.13

Hysteria2, VLESS-Reality, Trojan, VMess, AnyTLS, Shadowsocks-2022 — every protocol the VilaNet app supports, in one router-side binary. No external sing-box install needed.

LuCI + UCI native

Three LuCI tabs (Overview, Servers, Settings) plus the standard uci CLI. Anything you can flip in LuCI is also a UCI option — perfect for scripts, Ansible, or backup-restore.

Full-device TUN

When TUN is enabled, every LAN client's traffic enters the tunnel — including DNS over the encrypted path. No per-device config, no PAC files, no client-side leaks.

Kill-switch

When the tunnel drops, the firewall blocks LAN forwarding paths that would bypass it — a fail-closed allow-list keeps essential maintenance paths reachable. Survives DHCP renewals and link bounces.

Per-device credential vault

Account credentials are sealed in an AES-256-GCM envelope keyed off a 32-byte per-device secret via HKDF. A casual strings dump reveals nothing useful, and a copied envelope file won't decrypt on a different router.

Installation

Pick your architecture

Pre-built IPKs for the latest release are published on the GitHub releases page. Pick the architecture that matches opkg print-architecture on your router.

x86_64 — VMs, mini-PCs, modern routers

For Proxmox VMs, generic x86 routers, OPNsense-killer boxes, and OpenWrt-on-laptop builds. Verify with opkg print-architecture — you should see x86_64 in the output.

# 1. Download the IPKs for your architecture from https://github.com/vilavpn/vilanet-openwrt/releases, then SCP them to the router $ scp vilanet-core_1.0.0_x86_64.ipk [email protected]:/tmp/ $ scp luci-app-vilanet_1.0.0_all.ipk [email protected]:/tmp/ # 2. SSH in and install $ ssh [email protected] router:~# opkg update router:~# opkg install /tmp/vilanet-core_1.0.0_x86_64.ipk router:~# opkg install /tmp/luci-app-vilanet_1.0.0_all.ipk router:~# /etc/init.d/vilanet enable router:~# vilanet version # sanity check
opkg ≥24.10 required: The IPK uses the tar/ustar 1.0 format. Older opkg builds (the ar-format dialect) will reject it. Newer OpenWrt releases accept both.

aarch64 — Raspberry Pi, modern ARM routers

For Raspberry Pi 4/5 running OpenWrt, modern ARM-based travel routers, and aarch64 VMs. Pick the aarch64_cortex-a53 or aarch64_generic variant that matches opkg print-architecture.

$ scp vilanet-core_1.0.0_aarch64_cortex-a53.ipk [email protected]:/tmp/ $ scp luci-app-vilanet_1.0.0_all.ipk [email protected]:/tmp/ router:~# opkg update router:~# opkg install /tmp/vilanet-core_1.0.0_aarch64_cortex-a53.ipk router:~# opkg install /tmp/luci-app-vilanet_1.0.0_all.ipk router:~# /etc/init.d/vilanet enable

mipsel_24kc — older TP-Link, Xiaomi, Netgear

For older 32-bit MIPS-LE routers. RAM is tight on these boards — expect ~25–40 MB resident for the sing-box runtime under load. If opkg refuses to install due to free-space, swap-on-USB usually fixes it.

$ scp vilanet-core_1.0.0_mipsel_24kc.ipk [email protected]:/tmp/ $ scp luci-app-vilanet_1.0.0_all.ipk [email protected]:/tmp/ router:~# opkg install /tmp/vilanet-core_1.0.0_mipsel_24kc.ipk router:~# opkg install /tmp/luci-app-vilanet_1.0.0_all.ipk
Throughput note: Cheap MIPS hardware tops out around 30–80 Mbps through the tunnel because crypto is software-only. If you need gigabit, use x86_64 or aarch64 hardware.
Which architecture do I have? SSH into the router and run opkg print-architecture. The last column of any line is the architecture string — pick the IPK whose filename ends in that suffix. If you see something exotic like arm_cortex-a7_neon-vfpv4, contact [email protected] with that output and we'll point you at the right build.
Quick Start

From flashed router to working tunnel

Both IPKs installed? These four steps take you from a freshly-flashed router to every LAN device exiting through VilaVPN. Run them from the router's shell.

1. Authenticate

Log in once. The password is sealed into /etc/vilanet/.credentials with AES-256-GCM — no plaintext on flash.

router:~# vilanet login Email: [email protected] Password: # typed silently, sealed with AES-256-GCM Login successful.

2. Browse your nodes

List packages and servers. Node IDs are stable hex digests — safe to scriptify or paste into UCI.

router:~# vilanet packages router:~# vilanet servers router:~# vilanet servers --country HK

3. Connect

Connect to a country, a specific node ID, or let auto-select choose. The service brings up sing-box on TUN and updates the firewall rules in one go.

router:~# vilanet connect -country HK Connected to HK-01 (Hong Kong) — exit 198.51.100.42 # Or by node ID for stable scripts: router:~# vilanet connect -node 7a4f2e1b8c3d9f5e # Or via UCI + init script (survives reboots): router:~# uci set vilanet.global.selected_server=7a4f2e1b8c3d9f5e router:~# uci commit vilanet router:~# /etc/init.d/vilanet restart

4. Verify on a LAN client

From any device on the LAN, check the public IP. It should match the exit node — proof that traffic is taking the encrypted path.

# On a laptop / phone connected to the router's LAN $ curl https://ifconfig.me 198.51.100.42 # <- VilaNet HK exit, not your ISP # From the router itself, check live status router:~# vilanet status Connected · HK-01 · 198.51.100.42 Uptime: 00:14:32 Tx/Rx: 2.4 MB / 18.7 MB Protocol: Hysteria2
How traffic flows

From LAN port to exit node

Every LAN client's packets are intercepted by the router's TUN device, handed to the embedded sing-box for protocol multiplexing, encrypted, and sent over the WAN port to a VilaVPN exit node. DNS rides the same encrypted path; the kill-switch firewall rules ensure nothing leaks if the tunnel drops.

┌────────────────────┐ │ LAN clients │ phones · TVs · IoT · laptops · consoles └─────────┬──────────┘ │ unmodified packets via br-lan ▼ ┌────────────────────┐ │ OpenWrt firewall │ fw4 / nftables — kill-switch rules live here │ + TUN inbound │ catches all LAN-egress when tun.enabled=1 └─────────┬──────────┘ │ packets reach the vilanet daemon ▼ ┌────────────────────┐ │ sing-box (embedded)│ generated config from UCI │ DNS-over-HTTPS │ block_dot · block_quic · block_stun (tri-state) │ geoip + geosite │ bypass_china · custom domain rules └─────────┬──────────┘ │ encrypted: Hysteria2 / VLESS-Reality / Trojan / VMess ▼ ┌────────────────────┐ │ VilaNet exit node │ high-throughput, resilient └─────────┬──────────┘ │ ▼ The open internet
LuCI

Three tabs do everything

After installing the LuCI app, open http://router.lan/cgi-bin/luci/admin/network/vilanet in a browser. Sign in with the same credentials you use for OpenWrt's admin UI. You'll see three tabs.

Sign in

First visit shows the sign-in card on the Overview tab. Enter your VilaVPN email and password — credentials are sealed into the AES-256-GCM envelope at /etc/vilanet/.credentials on the router. Nothing is stored in the browser.

LuCI VilaNet login screen — email and password fields with the Login button
Overview · not signed in The sign-in card on a fresh router. After login, this same tab shows live status, account info, and quick actions.

Overview · post-login

After signing in, Overview becomes the live dashboard: VPN service state, selected server, routing mode, account, and a Quick Actions panel for connect / disconnect / mode switching without leaving the page.

LuCI VilaNet Overview — running, connected, signed in, with Quick Actions and Configuration Snapshot
Overview · connected to HK-Nube VPN service running, connection connected, selected node visible, account signed in. The Quick Actions block lets you connect/disconnect/swap mode in place; Configuration Snapshot echoes the most useful UCI knobs as a sanity check.

Servers · pick any node

The full server catalogue with a live search filter and a per-row Connect button. Rows show the node name and country; the currently selected node ID is pinned at the top.

LuCI VilaNet Servers tab — nodes across DE / HK / JP / SG / TW / UK / US with per-row Connect buttons
Servers · pick any node Hysteria2, Shadowsocks-IPLC, and IPv6 nodes across DE, HK, JP, SG, TW, UK, US. Type into Search to filter; click Connect to switch nodes in place — the router restarts sing-box with the new selection.

Settings · every UCI knob

All UCI keys surfaced as form fields: auto-connect, domain strategy, tunnel/direct DNS, MTU, LAN proxy sharing, authentication. Save Settings writes UCI and restarts the service in one step.

LuCI VilaNet Settings tab — form fields for every UCI key with Save Settings and Reset buttons
Settings · all UCI keys Same fields as the UCI reference table below, in form-field shape. Save Settings performs the uci set + uci commit + /etc/init.d/vilanet restart cycle for you.
Command reference

vilanet on the shell

Every LuCI action has a CLI equivalent. Run vilanet help for the index, or vilanet help <verb> for per-command flags. All output is plain text by default — pass -json on read-only commands to get machine-readable output.

login

Interactive prompt → seals email + password into the AES-256-GCM credentials envelope. Idempotent.

logout

Erases stored credentials and the cached package list. Idempotent.

status

Live state: connected node, public exit IP, uptime, Tx/Rx, protocol. Returns exit 0 when connected, 3 when idle.

connect

Bring up the tunnel. Flags: -country ISO, -node ID, -package NAME. With no flags, picks from the active UCI selection.

disconnect

Tear down the tunnel. Restores baseline firewall rules. Idempotent.

servers

List nodes. Filter with -country / -package. Add -json for scripting.

packages

List packages on your account: name, node count, system type, due date.

mode

Switch routing mode: global, rule (smart bypass), or direct (no proxy rules). Persists to UCI and restarts the service.

config

Read or write any UCI key. Sub-verbs: show, get, set, show-singbox. Setting auth.email or auth.password is rejected with a redirect to vilanet login.

diagnostics

Writes a redacted .tar.gz support bundle: log tail, redacted UCI, public-node DTOs, environment. Mode 0600. Safe to email to support.

version

Print version, sing-box version, build hash. Useful in bug reports.

Try it now

Interactive terminal simulator

Type any vilanet command below and see the (simulated) output. Or click a chip to drop in a ready-made example. No router required — it all runs in your browser.

vilanet version vilanet login vilanet servers --country HK vilanet connect -country HK vilanet status vilanet disconnect vilanet config show vilanet config show-singbox vilanet mode rule vilanet diagnostics vilanet help
[email protected] — vilanet simulator
router:~#
Note: the simulator reproduces the look of real router output but does not connect to anything — it can't reveal your account info or actually establish a tunnel.
UCI

/etc/config/vilanet — every knob

Every LuCI form field is backed by a UCI option in /etc/config/vilanet. Read with uci show vilanet; write with uci set vilanet.<section>.<key>=<value> && uci commit vilanet. Changes take effect after /etc/init.d/vilanet restart.

global

KeyValuesDescription
enabled0 · 1Run on boot when 1. Set by connect / disconnect.
auto_connect0 · 1Bring up the tunnel automatically when the service starts.
auto_reconnect0 · 1Re-dial the same node on transient failure.
selected_servernode IDStable hex digest of the active node.
selected_packagepackage IDConstrains selected_server to a package.
connection_modeglobal · ruleLegacy routing mode key; superseded by routing_mode internally.
log_levelerror · warnVerbosity for /var/log/vilanet.log. Higher levels are rejected at write time to keep config details out of logs.

network

KeyValuesDescription
domain_strategyipv4_only · prefer_ipv4 · prefer_ipv6How the resolver mixes A and AAAA.
dns_remotehttps://1.0.0.1/dns-queryDoH endpoint used over the tunnel.
dns_locale.g. 223.5.5.5Bypass resolver — used for direct-route names.
bypass_china0 · 1Apply the geosite-CN / geoip-CN smart bypass rules.
bypass_lan0 · 1Keep RFC1918 traffic on the direct path.
sniff_enabled0 · 1Leading sniff rule on inbound — needed for SNI-based routing.
mux_enabled0 · 1Multiplex outbound where the protocol supports it.
tun_enabled0 · 1Whole-router TUN capture. Default for headless routers.
dns_modefakeip · realsing-box DNS strategy — fake-ip is faster, real-DNS works with apps that don't honour DNS.
block_ads0 · 1Geosite-category-ads reject.
block_porn0 · 1Geosite-category-porn reject.
block_dotdefault · 0 · 1Tri-state. default = on. Blocks DNS-over-TLS on the LAN to force resolution through the tunnel.
block_quicdefault · 0 · 1Tri-state. default = on. Blocks QUIC so DNS leaks via HTTP/3 are eliminated.
block_stundefault · 0 · 1Tri-state. default = off. Blocks STUN endpoint discovery — useful for WebRTC IP leak prevention.
enable_ipv60 · 1Allow IPv6 over the tunnel. Off by default — many exit nodes are v4-only.
mtue.g. 1420TUN MTU. Lower if you see fragmentation on bridged uplinks.

proxy (LAN SOCKS sharing)

KeyValuesDescription
enabled0 · 1Expose SOCKS5+HTTP on the LAN. Opt-in — off by default.
port10081Mixed-inbound listen port.
auth_enabled0 · 1Require username + password.
auth_userstringProxy username.
auth_passstringProxy password.

kill_switch

KeyValuesDescription
enabled0 · 1Block LAN forwarding paths that would bypass the tunnel. The fail-closed allow-list is maintained internally — no manual sync needed.
If TUN isn't active: Enabling the kill-switch without TUN running will interrupt LAN internet during service restarts. For headless routers, leave tun.enabled=1 as well.

hy2 (Hysteria2 tuning)

KeyValuesDescription
speed_modeoff · server_default · customDefault off (BBR). server_default uses the package's advertised cap. custom honours custom_mbps.
custom_mbpse.g. 200Mbit/s in both directions. Only used when speed_mode='custom'.
hop_intervalseconds, e.g. 30Port-hopping interval — improves resilience under aggressive throttling.

domains (custom rules)

Custom rules apply before the geosite block, so they override defaults. Each entry is <domain> <action> where action is bypass, proxy, or block. Wildcards: *.youtube.com matches subdomains; bare youtube.com matches exactly.

router:~# uci add_list vilanet.domains.entry='*.youtube.com proxy' router:~# uci add_list vilanet.domains.entry='github.com bypass' router:~# uci add_list vilanet.domains.entry='ads.example.net block' router:~# uci commit vilanet router:~# /etc/init.d/vilanet restart

clash_api (off by default)

KeyValuesDescription
enabled0 · 1Expose sing-box's clash-api on 127.0.0.1:<port>. Router product is headless, so off by default.
port9090Clash-API listen port. Loopback only.
secretstringAPI token. Shown as secret_set: true in config show, never as plaintext.
Apply changes: Every uci set must be paired with uci commit vilanet to persist, and /etc/init.d/vilanet restart to take effect. LuCI's Save & Apply does all three for you.
On-disk layout

Where everything lives on the router

The IPK is well-behaved: every file goes to a predictable path. Useful for backup-restore, Ansible playbooks, and post-mortem debugging.

PathPurpose
/usr/bin/vilanetService binary (single static Go executable, embedded sing-box).
/etc/init.d/vilanetprocd init script: start · stop · restart · enable.
/etc/config/vilanetUCI config — the table above documents every key.
/etc/vilanet/device.secret32-byte per-device secret. Mode 0400. Never back this up to git or cloud storage.
/etc/vilanet/.credentialsAES-256-GCM-sealed credential blob. Mode 0600. Legacy plaintext is migrated on first read.
/var/log/vilanet.logRolling daily log file. vilanet diagnostics bundles the tail.
/var/run/vilanet.pidprocd PID file.
/var/lib/vilanet/State directory — sing-box working state, cached package list.
/usr/libexec/rpcd/vilanetrpcd shim — LuCI talks to vilanet through this.
/www/luci-static/resources/view/vilanet/LuCI JS views: overview.js, servers.js, settings.js.
Treat device.secret as a secret: If device.secret leaks, the AES-256-GCM credentials envelope is decryptable. Exclude it from cloud backups, Ansible vaults, and config-export images. Re-running vilanet login after a fresh generation invalidates anything sealed against the old secret.
Troubleshooting

When something's off, start here

opkg refuses to install (Cannot satisfy the following dependencies / Malformed package)

The IPK ships in tar/ustar 1.0 format. Older OpenWrt builds with the ar-format opkg dialect reject it. Upgrade to OpenWrt ≥24.10, or use the matching archive/ snapshot for your release.

vilanet status says "idle" but connect just printed Connected

Check the log: logread -e vilanet and tail -200 /var/log/vilanet.log. The most common cause is a routing-mode/DNS mismatch (R1 — fixed in v0.2.0). If you're on an older build, try vilanet mode rule as a workaround, then upgrade.

LAN clients get connectivity drops every few seconds

Almost always MTU. Drop network.mtu from 1420 to 1380 (some PPPoE uplinks) or 1280 (encapsulated WAN). Restart the service after each change.

YouTube / Netflix is slow, but speedtest is fine

Browser is preferring QUIC. With block_quic=1 (the default) sing-box rejects UDP/443, forcing the browser to fall back to TCP. Either accept the fallback (slight loss in throughput) or set block_quic=0 if you accept the DNS-leak risk.

"Failed to bind TUN" / "operation not permitted"

TUN needs the tun kernel module. opkg install kmod-tun if missing. Some minimal OpenWrt images strip it.

Login keeps failing with "invalid credentials" — but the password is right

Probably a stale envelope from before device.secret was rotated. vilanet logout && vilanet login reseats both files. If that fails, delete /etc/vilanet/.credentials manually and log in again.

Need to ship a bug report

Run vilanet diagnostics. It writes a redacted .tar.gz bundle (log tail, redacted UCI, public-node DTOs, environment) to /var/lib/vilanet/diag/. Mode 0600. Email the file to [email protected].

Live log tailing: For most problems, logread -f | grep -i vilanet in one window and the failing command in another tells you everything.
AI assistants

Drive vilanet-openwrt with your AI

vilanet-openwrt ships a universal Agent Skill at ai/vilanet-openwrt/SKILL.md — a single plain-markdown file that teaches Claude Code, Codex CLI, Gemini CLI, Cursor, Cline, Aider, Copilot CLI, and any other modern AI assistant how to drive the router-side VPN on your behalf. Install on the machine you SSH into the router from, then ask your AI to install the IPK, flip UCI keys, drive ubus call vilanet ..., or diagnose a stuck connection.

Claude Code

Drop the skill file into ~/.claude/skills/vilanet-openwrt/SKILL.md on your laptop. Claude Code auto-discovers it on next start, so any new session can install IPKs, flip UCI keys, and drive ubus end-to-end.

$ mkdir -p ~/.claude/skills/vilanet-openwrt $ curl -fsSL https://testingcf.jsdelivr.net/gh/vilavpn/vilanet-openwrt@main/ai/vilanet-openwrt/SKILL.md \ -o ~/.claude/skills/vilanet-openwrt/SKILL.md

Gemini CLI ≥ 0.41

Use native Agent Skills support. From a local checkout the skills link form is the fastest path; on older Gemini builds, append a @./relative-path import to GEMINI.md instead.

$ gemini skills install https://github.com/vilavpn/vilanet-openwrt.git --path ai/vilanet-openwrt # Or from a local checkout: $ gemini skills link ./ai/vilanet-openwrt

Codex CLI · Cursor · Cline · Aider · Copilot CLI

The skill content is identical across every tool — only the install path differs. Write to a neutral location, then point your AI at it (and, for tools that read AGENTS.md, append idempotently rather than overwriting).

$ mkdir -p .agents/skills/vilanet-openwrt $ curl -fsSL https://testingcf.jsdelivr.net/gh/vilavpn/vilanet-openwrt@main/ai/vilanet-openwrt/SKILL.md \ -o .agents/skills/vilanet-openwrt/SKILL.md # For tools that only read AGENTS.md, append (idempotent — re-runs are a no-op): $ grep -qxF '## VilaNet OpenWrt Agent Skill' AGENTS.md 2>/dev/null || \ { echo; echo '## VilaNet OpenWrt Agent Skill'; cat .agents/skills/vilanet-openwrt/SKILL.md; } >> AGENTS.md

Once installed, here's how you talk to it

You don't need to memorise UCI keys, ubus methods, or LuCI menus. Once the skill is loaded, describe what you want in plain language — examples below are the kind of thing you can paste into your AI chat.

Install the vilanet-core and luci-app-vilanet IPKs on my router at 192.168.1.1 — it's running OpenWrt 24.10.2 on x86_64.
Log in to my VilaVPN account and connect the router to a Hong Kong exit.
The LuCI Overview tab keeps showing the sign-in form even though I logged in via SSH. Diagnose and fix it.
Set network.dns_mode=real and routing_mode=direct, then restart so the config takes effect.
Block STUN, DoT, and QUIC across the LAN so DNS can't leak around the tunnel.
Switch routing to global so even mainland China traffic goes through the VPN, and reconnect.
My exit IP didn't change after vilanet connect. Pull a vilanet diagnostics bundle and tell me what's wrong.

Your AI translates the request into the right vilanet / uci / ubus invocation, runs it over your existing SSH session, and explains the result back in plain language.