mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 07:37:15 +00:00
feat(amneziawg): add AmneziaWG as an outbound protocol (#6320)
* feat(amneziawg): add AmneziaWG as an outbound protocol - AmneziaWG outbound protocol end-to-end: config schema, socks bridge, netstack, panel UI - Route amneziawg outbounds to HTTP probe in TCP mode (backend + frontend classifiers) with pinning test - Add 2-minute idle read deadline to pumpUDPEgress to reap idle egress sessions - Require SOCKS5 username/password auth on the egress server (reject NO-AUTH with 0xFF) with test - Bound the egress TCP tunnel dial with portForwardDialTimeout (10s), matching portfwd.go - Resolve UDP domain targets off the association's reader loop via deliverUDPDatagram; race-safe getOrDial starts the reply pump at session creation; client passed by value into resolver goroutines (pinned by TestEgressUDPDatagramDomainInterleavedClients) - Reconcile early-returns on an empty desired set and closes the egress listener; EgressBasePort (64900) is reserved against local inbound port conflicts like the internal API port, with pinning tests for both the port reservation (TestCheckPortConflict_EgressPortBlockedLocal) and the Reconcile empty-desired Close/Listen lifecycle (TestOutboundManagerReconcileEmptyDesiredClosesEgress) - Eliminate acceptLoop shutdown race by validating listener != nil and registering to tracked under s.mu before wg.Add; bound pre-auth handshake with deadline (pinned by TestEgressServerCloseDuringConcurrentAccepts) - Support AAAA and dual-stack domain resolution in tunnel DNS resolver with v6 default fallback (DefaultTunnelDNSServerV6); add DNS field to frontend protocol form; avoid unneeded cache flushes on unchanged SetStack ticks * fix(amneziawg): resolve IPv6-only DNS default fallback and validate required keys - Default to IPv6 tunnel DNS on IPv6-only outbounds with blank dns - Require non-empty secretKey and peer publicKey in ValidateAmneziaWGOutbound - Add end-to-end IPv6 tunnel domain resolution test and test empty key rejection - Trim comment blocks exceeding 2 lines across modified files - Fix Storybook test execution on environments with POSIX locale Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { VmessSecuritySchema } from '@/schemas/protocols/shared/vmess';
|
||||
import { SecuritySettingsSchema } from '@/schemas/protocols/security';
|
||||
import { NetworkSettingsSchema, StreamExtrasSchema } from '@/schemas/protocols/stream';
|
||||
import {
|
||||
AmneziaWGOutboundSettingsSchema,
|
||||
BlackholeResponseTypeSchema,
|
||||
DNSRuleActionSchema,
|
||||
FreedomFinalRuleActionSchema,
|
||||
@@ -112,6 +113,11 @@ export const WireguardOutboundFormSettingsSchema = z.object({
|
||||
});
|
||||
export type WireguardOutboundFormSettings = z.infer<typeof WireguardOutboundFormSettingsSchema>;
|
||||
|
||||
// Re-export under the form name: the form state IS the wire shape (flat
|
||||
// obfuscation fields, same as the inbound server block), so no rename layer.
|
||||
export const AmneziaWGOutboundFormSettingsSchema = AmneziaWGOutboundSettingsSchema;
|
||||
export type AmneziaWGOutboundFormSettings = z.infer<typeof AmneziaWGOutboundFormSettingsSchema>;
|
||||
|
||||
// Hysteria outbound carries the connect target only; transport-layer knobs
|
||||
// (auth, congestion, up/down, hop port, timeouts) ride on stream.hysteria.
|
||||
export const HysteriaOutboundFormSettingsSchema = z.object({
|
||||
@@ -194,6 +200,7 @@ export const OutboundFormSettingsSchema = z.discriminatedUnion('protocol', [
|
||||
z.object({ protocol: z.literal('socks'), settings: SocksOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('http'), settings: HttpOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('wireguard'), settings: WireguardOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('amneziawg'), settings: AmneziaWGOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('hysteria'), settings: HysteriaOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('freedom'), settings: FreedomOutboundFormSettingsSchema }),
|
||||
z.object({ protocol: z.literal('blackhole'), settings: BlackholeOutboundFormSettingsSchema }),
|
||||
|
||||
@@ -7,6 +7,7 @@ export const OutboundProtocols = Object.freeze({
|
||||
Trojan: 'trojan',
|
||||
Shadowsocks: 'shadowsocks',
|
||||
Wireguard: 'wireguard',
|
||||
AmneziaWG: 'amneziawg',
|
||||
Hysteria: 'hysteria',
|
||||
Socks: 'socks',
|
||||
HTTP: 'http',
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Wire format of an "amneziawg" OUTBOUND settings block; form-edited and
|
||||
// backend-validated, swapped for a socks bridge at config generation.
|
||||
export const AmneziaWGOutboundPeerSchema = z.object({
|
||||
publicKey: z.string().default(''),
|
||||
presharedKey: z.string().default(''),
|
||||
allowedIPs: z.array(z.string()).default(['0.0.0.0/0', '::/0']),
|
||||
endpoint: z.string().default(''),
|
||||
keepAlive: z.number().int().min(0).default(0),
|
||||
});
|
||||
export type AmneziaWGOutboundPeer = z.infer<typeof AmneziaWGOutboundPeerSchema>;
|
||||
|
||||
export const AmneziaWGOutboundSettingsSchema = z.object({
|
||||
// 0 = unset, so the backend derives MTU from S4 (amneziawg.EffectiveMTU);
|
||||
// a pinned 1420 fragments every full-size packet once S4 exceeds 20.
|
||||
mtu: z.number().int().min(0).default(0),
|
||||
secretKey: z.string().default(''),
|
||||
address: z.array(z.string()).default([]),
|
||||
listenPort: z.number().int().min(0).max(65535).default(0),
|
||||
dns: z.string().default(''),
|
||||
jc: z.number().int().min(0).default(0),
|
||||
jmin: z.number().int().min(0).default(40),
|
||||
jmax: z.number().int().min(0).default(100),
|
||||
s1: z.number().int().min(0).default(15),
|
||||
s2: z.number().int().min(0).default(80),
|
||||
s3: z.number().int().min(0).max(64).default(12),
|
||||
s4: z.number().int().min(0).max(32).default(12),
|
||||
h1: z.string().default(''),
|
||||
h2: z.string().default(''),
|
||||
h3: z.string().default(''),
|
||||
h4: z.string().default(''),
|
||||
i1: z.string().default(''),
|
||||
i2: z.string().default(''),
|
||||
i3: z.string().default(''),
|
||||
i4: z.string().default(''),
|
||||
i5: z.string().default(''),
|
||||
headerProtectionKey: z.string().default(''),
|
||||
contentPaddingAddition: z.string().default(''),
|
||||
rekeyAfterTime: z.string().default(''),
|
||||
rekeyTimeout: z.string().default(''),
|
||||
rejectAfterTime: z.string().default(''),
|
||||
keepaliveTimeout: z.string().default(''),
|
||||
maxHandshakeAttempts: z.string().default(''),
|
||||
randomTrailers: z.boolean().default(false),
|
||||
disableCookies: z.boolean().default(true),
|
||||
peers: z.array(AmneziaWGOutboundPeerSchema).default([]),
|
||||
});
|
||||
export type AmneziaWGOutboundSettings = z.infer<typeof AmneziaWGOutboundSettingsSchema>;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { BlackholeOutboundSettingsSchema } from './blackhole';
|
||||
import { AmneziaWGOutboundSettingsSchema } from './amneziawg';
|
||||
import { DNSOutboundSettingsSchema } from './dns';
|
||||
import { FreedomOutboundSettingsSchema } from './freedom';
|
||||
import { HttpOutboundSettingsSchema } from './http';
|
||||
@@ -14,6 +15,7 @@ import { VmessOutboundSettingsSchema } from './vmess';
|
||||
import { WireguardOutboundSettingsSchema } from './wireguard';
|
||||
|
||||
export * from './blackhole';
|
||||
export * from './amneziawg';
|
||||
export * from './dns';
|
||||
export * from './freedom';
|
||||
export * from './http';
|
||||
@@ -32,6 +34,7 @@ export const OutboundSettingsSchema = z.discriminatedUnion('protocol', [
|
||||
z.object({ protocol: z.literal('trojan'), settings: TrojanOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('shadowsocks'), settings: ShadowsocksOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('wireguard'), settings: WireguardOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('amneziawg'), settings: AmneziaWGOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('hysteria'), settings: HysteriaOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('http'), settings: HttpOutboundSettingsSchema }),
|
||||
z.object({ protocol: z.literal('socks'), settings: SocksOutboundSettingsSchema }),
|
||||
|
||||
Reference in New Issue
Block a user