mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-14 09:55:01 +03:00
feat(amneziawg): Phase 2a — IPv6 support + NDP proxy
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>
This commit is contained in:
@@ -654,6 +654,9 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"h3": "",
|
||||
"h4": "",
|
||||
"i1": "",
|
||||
"ipv6Enabled": false,
|
||||
"ipv6ExternalInterface": "",
|
||||
"ipv6Subnet": "",
|
||||
"jc": 0,
|
||||
"jmax": 0,
|
||||
"jmin": 0,
|
||||
|
||||
@@ -2795,6 +2795,16 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
>
|
||||
<Input placeholder="eth0" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name={['settings', 'server', 'ipv6Enabled']}
|
||||
label={t('pages.xray.amneziawg.ipv6Enabled')}
|
||||
valueProp="checked"
|
||||
>
|
||||
<Switch />
|
||||
</FormField>
|
||||
<FormField
|
||||
name={['settings', 'server', 'ipv6Subnet']}
|
||||
label={t('pages.xray.amneziawg.ipv6Subnet')}
|
||||
extra={t('pages.xray.amneziawg.ipv6SubnetHint')}
|
||||
>
|
||||
<Input placeholder="fd86:ea04:1115::/64" />
|
||||
</FormField>
|
||||
<FormField
|
||||
name={['settings', 'server', 'ipv6ExternalInterface']}
|
||||
label={t('pages.xray.amneziawg.ipv6ExternalInterface')}
|
||||
extra={t('pages.xray.amneziawg.ipv6ExternalInterfaceHint')}
|
||||
>
|
||||
<Input placeholder="eth0" />
|
||||
</FormField>
|
||||
<Form.Item label={t('pages.xray.amneziawg.obfuscation')}>
|
||||
<Button icon={<ReloadOutlined />} onClick={regenInboundAwgObfuscation}>
|
||||
{t('pages.xray.amneziawg.regenerateObfuscation')}
|
||||
|
||||
@@ -46,6 +46,9 @@ export const AmneziawgServerSchema = z.object({
|
||||
primaryDns: z.string().default('8.8.8.8'),
|
||||
secondaryDns: z.string().default('8.8.4.4'),
|
||||
externalInterface: z.string().default(''),
|
||||
ipv6Enabled: z.boolean().default(false),
|
||||
ipv6Subnet: z.string().default(''),
|
||||
ipv6ExternalInterface: z.string().default(''),
|
||||
jc: z.number().int().min(0).default(5),
|
||||
jmin: z.number().int().min(0).default(10),
|
||||
jmax: z.number().int().min(0).default(50),
|
||||
|
||||
Reference in New Issue
Block a user