defaultWireguardClients/defaultAmneziaWGClients only ever checked uniqueness
against their own inbound's client list, so two inbounds sharing a subnet
(same protocol or not) could silently hand out or accept the same address --
the exact scenario behind a real duplicate-IP incident where a WireGuard and
an AmneziaWG client both ended up on the same address. otherTunnelAllowedIPs
now collects every address already claimed on every other tunnel inbound and
folds it into both the auto-allocation pool and the manual-entry collision
check, naming the other inbound in the error when it fires.
Adds native dual-stack IPv6 to AmneziaWG inbounds, ported from
coinman-dev/3ax-ui's approach:
- ServerSettings gets ipv6Enabled/ipv6Subnet/ipv6ExternalInterface;
Instance carries the server's own IPv6 address (first host of the
subnet) alongside its IPv4 one.
- defaultAmneziaWGClients allocates an IPv6 host address per client
(second AllowedIPs entry) when the server has IPv6 enabled, reusing
allocateWireguardAddress — which needed a real fix along the way: it
always suffixed "/32" regardless of address family, which is wrong
for an IPv6 host address (needs /128). Now family-aware.
- generateServerConfig's PostUp/PostDown gains IPv6 forward-accept
rules, proxy_ndp sysctl, and one `ip -6 neigh add/del proxy` entry per
enabled peer with an IPv6 address — the lightweight per-client
method, not the ndppd-daemon whole-subnet method (not worth the
config-file-management complexity at this scale; ndppd itself is
still installed by install.sh in case that changes later).
- ValidateIPv6Subnet rejects a malformed subnet before save.
- Frontend: ipv6Enabled/ipv6Subnet/ipv6ExternalInterface fields on the
AmneziaWG inbound form, EN+RU translations, openapi.json/generated/*
regenerated (the latter via `go run ./tools/openapigen`, pure Go).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AmneziaWG (WireGuard plus DPI-resistant obfuscation) needs no Docker
here — it runs as a genuine kernel interface via awg-quick/awg, managed
the same way internal/mtproto manages mtg: one Inbound row is one
desired Instance, and a Manager reconciles running interfaces toward
the database every 10s (internal/web/job/amneziawg_job.go) plus
immediately after a client edit (applyLocalAmneziaWG).
Clients reuse model.Client verbatim (the same PrivateKey/PublicKey/
PreSharedKey/AllowedIPs fields WireGuard already uses), so bulk
operations, the QR/share-link modal and subscriptions come from the
shared inbound infrastructure instead of a parallel implementation.
internal/amneziawg owns the obfuscation param generator/validator
(ported from coinman-dev/3ax-ui, upgraded to AmneziaWG 2.0's S3/S4
padding and I1 signature packet) and the exec wrapper around
awg-quick/awg, with fingerprint-based reconcile (noop / reload-via-
syncconf / full restart) mirroring mtproto.Manager so a same-protocol
edit doesn't force an unnecessary interface bounce that would drop
every peer's connection.
Frontend and install.sh's DKMS/awg-tools setup are tracked separately;
this is backend-only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>