Same 8 findings fixed on upstream-pr/amneziawg, ported here since this
fork's internal/amneziawg + related web/service files predate that PR
branch's own fix-up commits:
1. hostRulesFingerprint now folds in a peer's IPv4 whenever
ForwardedPorts is set, not only when RouteThroughXray is on, so a
re-IP forces the bounce needed to move the DNAT rule too.
2. ValidateConfigValue (new, params.go) rejects control characters in
server/client keys, email and I1 at save time; sanitizeConfigValue
strips them defensively at .conf-render time.
3. checkForwardedPortsConflict now scopes to node_id IS NULL and takes
a pre-loaded portConflictContext (loadPortConflictContext), so a
port used only on another node isn't a false collision and an
inbound with N clients costs one query instead of N.
4. PostDown commands are now best-effort (appendOrTrue) so an external
firewall flush can't abort the rest of the teardown chain.
5. The "ip rule list | grep -q" existence check now uses
grep -c >/dev/null, avoiding a pipefail/SIGPIPE false negative that
could re-add a duplicate rule.
6. route_egress.go's stale "always present, no opt-in" comment
corrected to describe the real RouteThroughXray-gated behavior.
(This fork's genAmneziaWGLink already emits vpn://, and there's no
upstream-facing docs page here, so neither needed the PR branch's
Finding 6 docs/link-format changes.)
7. install.sh: Arch's ndppd install uses pacman -Sy, not -Syu, matching
every other pacman call in the script; should_install_amneziawg
short-circuits to yes when awg is already installed, so `x-ui
update` doesn't re-prompt -- this fork's own opt-out-by-default
philosophy for should_install_amneziawg is unchanged, only the
redundant-reprompt behavior is fixed.
8. CollectTraffic checks pointer identity before writing back a
traffic-counter baseline, so a concurrent restart's freshly-reset
(empty) baseline can't be clobbered by stale pre-restart counters.
sweepOrphansLocked no longer permanently disables itself on a
transient os.ReadDir failure.
go build/vet/test and frontend typecheck/lint/build/vitest all pass.
- manager.go: serverAddress assumed subnetIp always ends in ".0"; a
base like "10.8.1.5" was used verbatim as the server's own address,
eventually colliding with peer allocation (which starts at .2
upward). Now derives the first host of the actual subnetIp/subnetCidr
network via netip, matching serverAddressV6's own approach. A /32
base (no host bits at all) is still used as-is. (Finding 12, partial
-- the /16 pool-widening half of this finding only exists on the
upstream-pr/amneziawg branch's merged client_wireguard.go, not here;
handled separately on that branch.)
- manager.go: ensureLocked carried the previous per-peer traffic
counters (`last`) forward even through a full restart, but
awg-quick down+up resets the kernel's own counters to zero -- the
next CollectTraffic computed a large negative delta (clamped to 0),
silently discarding real traffic. Extracted the decision into
nextTrafficBaseline: only a reload (syncconf) preserves the
baseline. (Finding 13)
- portfwd.go: exported ForwardedPortsInclude; inbound_amneziawg.go's
new checkForwardedPortsConflict uses it to reject, at save time, a
client's forwardedPorts that would DNAT the panel's own port or
another enabled inbound's port to the tunnel client --
portForwardLines has no destination restriction, so this collision
was previously silent. Wired into both the single-client update path
and the add-client path (client_inbound_apply.go), plus
normalizeAmneziaWGSettings for the whole-inbound save path. (Finding 14)
- inbound.go: InboundOption.AwgServer sent the whole ServerSettings
struct including PrivateKey to GetInboundOptions callers -- a
shared, admin-wide dropdown-filling endpoint the frontend's own
AwgServerOptionSchema never reads that field from. Redacted it
before assigning. (Finding 11)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>