Skip to content

luci-app-wg-obfuscator: add LuCI support for wg-obfuscator - #9001

Draft
ClusterM wants to merge 1 commit into
openwrt:masterfrom
ClusterM:luci-app-wg-obfuscator
Draft

luci-app-wg-obfuscator: add LuCI support for wg-obfuscator#9001
ClusterM wants to merge 1 commit into
openwrt:masterfrom
ClusterM:luci-app-wg-obfuscator

Conversation

@ClusterM

@ClusterM ClusterM commented Sep 3, 2026

Copy link
Copy Markdown

Pull request details

Description

Adds a LuCI interface for wg-obfuscator, the WireGuard traffic obfuscator.

The page sits under Services → WireGuard Obfuscator and is a client-side JavaScript view, so it depends on luci-base alone and needs no Lua runtime. It maps every UCI option that the daemon's configuration generator reads: listen address and port, target, obfuscation key, protocol masking, dummy padding, the idle/incoming/resolve timeouts, firewall mark, log destination and timestamps, maximum clients, and the static bindings used for two-way mode. Above the form it polls the service state through ubus service list, reports whether the generated configuration exists, counts the enabled instances and offers a restart button.

Validation uses the stock luci-base datatypes (hostport, port, ipaddr, range) instead of hand-written checks. The static bindings field is validated line by line against host:port:localport, accepting hostnames since the daemon resolves them.

Translations are included for German, Spanish, French, Brazilian Portuguese, Russian, Turkish, Ukrainian and Simplified Chinese; po/templates/wg-obfuscator.pot was generated with build/i18n-sync.sh.

Screenshot or video of changes (if applicable)

image

Maintainer (preferred)

@ClusterM
(New package. I am the upstream author of wg-obfuscator and will maintain this app.)


Tested on

OpenWrt version: OpenWrt 25.12.5 (r33051-f5dae5ece4) on a Cudy WR3000 v1 (mediatek/filogic)
LuCI version: luci-base 26.180.75667~128a781
Web browser(s): Google Chrome 150.0.7871.186 (Official Build) (64-bit)

Verified on that device:

  • the app and all eight luci-i18n-wg-obfuscator-* packages build and install cleanly
  • /admin/services/wg-obfuscator dispatches to the view and the menu dependency on /usr/bin/wg-obfuscator resolves
  • the ACL grants exactly what the view uses, and each call was exercised through a real LuCI session: service list for the status, and the init script restart, which regenerates the daemon configuration
  • all eight catalogs are delivered to the client by /cgi-bin/luci/admin/translations/<lang>

Checklist

  • (Nice to have) Includes what it depends on (e.g. openwrt/packages#pr-number in sister repo).

Depends on openwrt/packages#30441, which adds the wg-obfuscator daemon this app configures and which LUCI_DEPENDS references. Filed as a draft until that one lands.

@openwrt openwrt Bot added the add package Introduces a new package Makefile build script label Sep 3, 2026
"description": "Grant access to WireGuard Obfuscator configuration",
"read": {
"file": {
"/var/etc/wg-obfuscator.conf": [ "read" ]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs.stat() maps to ubus file.stat, which rpcd gates on the list permission, not read — see rpc_check_path(msg, R, "list", ...) in rpc_file_stat, file.c:734. Non-root sessions will therefore get access denied on the getConfigPresent() call, and since it is wrapped in L.resolveDefault(..., null) the status line silently shows "Not found" forever. The view never reads the file content, so list alone is enough.

Suggested change
"/var/etc/wg-obfuscator.conf": [ "read" ]
"/var/etc/wg-obfuscator.conf": [ "list" ]

Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read -> list is fixed, thanks. Left a follow-up on the new line about the /var -> /tmp symlink, which is a separate matter.


Generated by Claude Code

s = m.section(form.TypedSection);
s.anonymous = true;
s.render = function () {
poll.add(() => Promise.all([

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering the poller inside the section's render() leaks one poller per re-render: Map.save() ends in renderContents() (form.js:639), which calls this render() again, and poll.add() only dedupes on identical function identity (luci.js:1091) — a fresh arrow function each time. After N "Save & Apply" cycles the page fires N service list + file stat round trips per interval. Register the poll once from the view's render() (or keep a module-level reference and re-add the same function).


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, thanks


Generated by Claude Code

Comment on lines +146 to +148
o.password = true;
o.default = 'test';
o.rmempty = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting the shared obfuscation secret to test means a user who adds an instance and hits Save gets a publicly known key written to UCI. Leave it unset so the required-field check forces a real value.

Suggested change
o.password = true;
o.default = 'test';
o.rmempty = false;
o.password = true;
o.rmempty = false;

Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, thanks


Generated by Claude Code

"Project-Id-Version: wg-obfuscator 1.6\n"
"POT-Creation-Date: 2026-09-03 00:00+0000\n"
"PO-Revision-Date: 2026-09-03 00:00+0000\n"
"Last-Translator: AI Assistant\n"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CONTRIBUTING.md asks contributors to use Weblate rather than editing *.po directly; these eight catalogs are machine-generated and will be clobbered on the next Weblate sync. Please drop po/{de,es,fr,pt_BR,ru,tr,uk,zh_Hans}/ and ship only po/templates/wg-obfuscator.pot, letting Weblate pick the app up.


Generated by Claude Code

Comment on lines +140 to +141
o.placeholder = 'example.com:13255';
o.default = '10.13.1.100:13255';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: setting both placeholder and default makes the placeholder dead — the field is prefilled with the hardcoded RFC1918 address instead. Same pair on lines 153-154 for source_if.

Suggested change
o.placeholder = 'example.com:13255';
o.default = '10.13.1.100:13255';
o.placeholder = 'example.com:13255';

Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, thanks


Generated by Claude Code

@ClusterM
ClusterM force-pushed the luci-app-wg-obfuscator branch from 703e2ad to 2224627 Compare September 3, 2026 10:41
@ClusterM

ClusterM commented Sep 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Four of the five points are fixed in the amended commit; the translation one needs a maintainer decision.

file.stat needs list, not read — correct, and I confirmed it on a 25.12.5 target rather than taking it from the source alone. Using a session restricted to this app's ACL group (root has read='*' in /etc/config/rpcd and bypasses ACLs entirely, so it cannot show the difference):

ACL grants ubus call file stat
read denied
list allowed

So the status line really did read "Not found" forever for every non-root session. Changed to list, which is also the narrower right, since the view only stats the file and never reads it.

Poller leaking on re-render — fixed. The callback is now a module-level updateStatus registered from the view's render() instead of an arrow function created inside the section's render(), so Map.save() re-running the section no longer adds a poller.

Default obfuscation key — dropped here and in the daemon's shipped UCI config. The config generator in the packages PR no longer defaults it either, so a missing key is now a reported error rather than a known secret.

Dead placeholders — removed the default on target and source_if so the placeholders are visible.

The po/ catalogs — I would rather leave this to you. I read CONTRIBUTING.md the same way you do, and I have no objection to shipping only the .pot. What I am unsure about is whether removing the catalogs now means the app ships untranslated until Weblate picks it up, in which case keeping them for the initial merge and letting Weblate take over afterwards may be preferable. Happy to drop po/{de,es,fr,pt_BR,ru,tr,uk,zh_Hans}/ in the next push if that is the preference. Note the Russian catalog is not machine-generated, the others are.

Configures wg-obfuscator, the WireGuard traffic obfuscator, from the web
interface. The view maps every UCI option that the daemon's config
generator reads: listen address and port, target, obfuscation key,
protocol masking, dummy padding, the three timeouts, firewall mark,
logging and the static bindings used for two-way mode. It also reports
whether the service is running and its generated configuration exists,
and offers a restart button.

The interface is a client-side JavaScript view, so it depends on
luci-base alone and needs no Lua runtime. Translations are included for
German, Spanish, French, Brazilian Portuguese, Russian, Turkish,
Ukrainian and Simplified Chinese.

The daemon that consumes these options is submitted to the packages feed
separately, as "wg-obfuscator: add new package".

Signed-off-by: Alexey Cluster <cluster@cluster.wtf>
@ClusterM
ClusterM force-pushed the luci-app-wg-obfuscator branch from 2224627 to 54e044e Compare September 3, 2026 13:32
@ClusterM

ClusterM commented Sep 3, 2026

Copy link
Copy Markdown
Author

No new review comments on this PR. While fixing the matching fwmark point on the packages side, the form's range(0,65535) datatype was also replaced with a validator that accepts the same input the daemon does (decimal or 0x-prefixed hex, 0-65535), so a value such as 0xdead from the README can be entered in LuCI as well.

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commit.


Generated by Claude Code

"description": "Grant access to WireGuard Obfuscator configuration",
"read": {
"file": {
"/var/etc/wg-obfuscator.conf": [ "list" ]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list is the right permission now, but the path still won't match: rpc_check_path resolves symlinks and re-runs the ACL check against the realpath (file.c:269-277), and /var is a symlink to tmp (base-files/Makefile:224), so the grant is evaluated as /tmp/etc/wg-obfuscator.conf and non-root sessions still get EACCES. Grant both spellings, the way luci-app-adblock does for its /var/run paths.`` This is likely why the 25.12.5 test passed — that rpcd predates the symlink re-authorization.

Suggested change
"/var/etc/wg-obfuscator.conf": [ "list" ]
"/var/etc/wg-obfuscator.conf": [ "list" ],
"/tmp/etc/wg-obfuscator.conf": [ "list" ]

Generated by Claude Code

Comment on lines +222 to +223
o.placeholder = '0xdead';
o.default = '0';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the dead-placeholder pair is back here — the field prefills with 0, so the 0xdead hint never shows. The description already documents that 0 disables the mark, so the default can go.

Suggested change
o.placeholder = '0xdead';
o.default = '0';
o.placeholder = '0xdead';

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

add package Introduces a new package Makefile build script

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants