From ef13e8567ee196d3cb6b7c98000157171863e921 Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Sat, 25 Jul 2026 17:25:58 +0300 Subject: [PATCH] =?UTF-8?q?feat(amneziawg):=20Phase=202a=20=E2=80=94=20IPv?= =?UTF-8?q?6=20support=20+=20NDP=20proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/public/openapi.json | 9 ++ frontend/src/generated/examples.ts | 3 + frontend/src/generated/schemas.ts | 10 ++ frontend/src/generated/types.ts | 3 + frontend/src/generated/zod.ts | 3 + .../inbounds/form/protocols/amneziawg.tsx | 23 +++- .../schemas/protocols/inbound/amneziawg.ts | 3 + internal/amneziawg/manager.go | 108 +++++++++++++++--- internal/amneziawg/params.go | 21 ++++ internal/amneziawg/types.go | 18 +++ internal/web/service/client_amneziawg.go | 33 ++++-- internal/web/service/client_wireguard.go | 13 ++- internal/web/service/inbound_amneziawg.go | 3 + internal/web/translation/en-US.json | 5 + internal/web/translation/ru-RU.json | 5 + 15 files changed, 227 insertions(+), 33 deletions(-) diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index a37fd5f80..cbfb73d07 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -2821,6 +2821,15 @@ "i1": { "type": "string" }, + "ipv6Enabled": { + "type": "boolean" + }, + "ipv6ExternalInterface": { + "type": "string" + }, + "ipv6Subnet": { + "type": "string" + }, "jc": { "description": "Obfuscation20's fields, repeated flat (not embedded) rather than\nnested under their own key: encoding/json would happily inline an\nembedded Obfuscation20 the same way, but the frontend's Go->Zod/TS\ngenerator (tools/openapigen) does not — it emits a genuinely nested\n`obfuscation20` object, which would silently diverge from the real\nwire JSON. See Obfuscation() below for the manager-facing conversion.", "type": "integer" diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 8030a06d4..5b00bb56c 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -654,6 +654,9 @@ export const EXAMPLES: Record = { "h3": "", "h4": "", "i1": "", + "ipv6Enabled": false, + "ipv6ExternalInterface": "", + "ipv6Subnet": "", "jc": 0, "jmax": 0, "jmin": 0, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 510ee0a98..db99dc1df 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -2795,6 +2795,16 @@ export const SCHEMAS: Record = { "i1": { "type": "string" }, + "ipv6Enabled": { + "description": "IPv6Enabled turns on native IPv6 for clients: an IPv6 host address is\nallocated from IPv6Subnet alongside each client's IPv4 one, and the\nserver proxies NDP for each enabled client's address so upstream\nrouters see it as directly reachable (no NAT66). IPv6ExternalInterface\noverrides ExternalInterface for the NDP-proxy PostUp/PostDown entries\nspecifically; empty reuses ExternalInterface.", + "type": "boolean" + }, + "ipv6ExternalInterface": { + "type": "string" + }, + "ipv6Subnet": { + "type": "string" + }, "jc": { "description": "Obfuscation20's fields, repeated flat (not embedded) rather than\nnested under their own key: encoding/json would happily inline an\nembedded Obfuscation20 the same way, but the frontend's Go-\u003eZod/TS\ngenerator (tools/openapigen) does not — it emits a genuinely nested\n`obfuscation20` object, which would silently diverge from the real\nwire JSON. See Obfuscation() below for the manager-facing conversion.", "type": "integer" diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index 9717ec53a..249564095 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -637,6 +637,9 @@ export interface ServerSettings { h3: string; h4: string; i1?: string; + ipv6Enabled?: boolean; + ipv6ExternalInterface?: string; + ipv6Subnet?: string; jc: number; jmax: number; jmin: number; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index 35b602a85..827c8f961 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -676,6 +676,9 @@ export const ServerSettingsSchema = z.object({ h3: z.string(), h4: z.string(), i1: z.string().optional(), + ipv6Enabled: z.boolean().optional(), + ipv6ExternalInterface: z.string().optional(), + ipv6Subnet: z.string().optional(), jc: z.number().int(), jmax: z.number().int(), jmin: z.number().int(), diff --git a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx index b0481e8cf..76da588e6 100644 --- a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx +++ b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next'; -import { Button, Form, Input, InputNumber, Space } from 'antd'; +import { Button, Form, Input, InputNumber, Space, Switch } from 'antd'; import { ReloadOutlined } from '@ant-design/icons'; import { FormField } from '@/components/form/rhf'; @@ -47,6 +47,27 @@ export default function AmneziawgFields({ awgPubKey, regenInboundAwg, regenInbou > + + + + + + + + +