From 71dc4539706a6f538359c29fe711ebf758d18080 Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Sun, 26 Jul 2026 00:39:32 +0300 Subject: [PATCH] feat(amneziawg): make the Xray TPROXY bridge a per-inbound opt-in Addresses Finding 10 from the automated PR review: an always-on TPROXY bridge makes every AmneziaWG tunnel hard-depend on Xray being up (all traffic, including DNS, drops whenever Xray restarts), and forces a full awg-quick down+up bounce on any client add/remove/re-IP, permanently losing the syncconf fast path. Adds ServerSettings.RouteThroughXray (off by default): - defaultPostUpDown only emits the TPROXY/policy-route rules when it's on; a plain AmneziaWG tunnel now has zero Xray dependency out of the box. - structuralFingerprint covers it (toggling it changes whether PostUp/ PostDown contain any TPROXY rules at all -- structural, not a per-peer host-rule). hostRulesFingerprint's IPv4 tracking is now itself conditional on RouteThroughXray (and IPv6 tracking on IPv6Enabled), so an instance that never uses either keeps the syncconf fast path for a plain peer re-IP. - injectAmneziawgEgress only creates a bridge for inbounds that opted in; checkAmneziawgEgressConflict (the Finding-7 fix) now parses each candidate through InstanceFromInbound so a non-routed inbound's port is correctly never treated as reserved. - New inbound-level Switch in the AmneziaWG form; the actual outbound decision is still made entirely through the panel's stock Routing page, same as before -- only whether the bridge exists at all is now a choice. Translation keys added to all 13 locales in the same commit this time, not backfilled later (see Finding 9's lesson). Co-Authored-By: Claude Sonnet 5 --- frontend/public/openapi.json | 4 + frontend/src/generated/examples.ts | 1 + frontend/src/generated/schemas.ts | 4 + frontend/src/generated/types.ts | 1 + frontend/src/generated/zod.ts | 1 + frontend/src/lib/xray/inbound-defaults.ts | 1 + .../inbounds/form/protocols/amneziawg.tsx | 8 ++ .../schemas/protocols/inbound/amneziawg.ts | 1 + internal/amneziawg/manager.go | 116 ++++++++++-------- internal/amneziawg/manager_test.go | 73 ++++++++--- internal/amneziawg/types.go | 16 +++ internal/web/service/port_conflict.go | 13 +- internal/web/service/port_conflict_test.go | 36 +++++- internal/web/service/xray.go | 23 ++-- .../web/service/xray_config_inject_test.go | 18 ++- internal/web/translation/ar-EG.json | 2 + internal/web/translation/en-US.json | 2 + internal/web/translation/es-ES.json | 2 + internal/web/translation/fa-IR.json | 2 + internal/web/translation/id-ID.json | 2 + internal/web/translation/ja-JP.json | 2 + internal/web/translation/pt-BR.json | 2 + internal/web/translation/ru-RU.json | 2 + internal/web/translation/tr-TR.json | 2 + internal/web/translation/uk-UA.json | 2 + internal/web/translation/vi-VN.json | 2 + internal/web/translation/zh-CN.json | 2 + internal/web/translation/zh-TW.json | 2 + 28 files changed, 257 insertions(+), 85 deletions(-) diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 9bcb3a494..14c0cb997 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -2862,6 +2862,10 @@ "publicKey": { "type": "string" }, + "routeThroughXray": { + "description": "RouteThroughXray turns on this inbound's TPROXY-into-Xray bridge; see\nInstance.RouteThroughXray for what that means. Off by default.", + "type": "boolean" + }, "s1": { "type": "integer" }, diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 0d0391323..fa4bb8e94 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -666,6 +666,7 @@ export const EXAMPLES: Record = { "primaryDns": "", "privateKey": "", "publicKey": "", + "routeThroughXray": false, "s1": 0, "s2": 0, "s3": 0, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index b55c533f8..00da739f9 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -2836,6 +2836,10 @@ export const SCHEMAS: Record = { "publicKey": { "type": "string" }, + "routeThroughXray": { + "description": "RouteThroughXray turns on this inbound's TPROXY-into-Xray bridge; see\nInstance.RouteThroughXray for what that means. Off by default.", + "type": "boolean" + }, "s1": { "type": "integer" }, diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index dd06334ed..eec2ce600 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -649,6 +649,7 @@ export interface ServerSettings { primaryDns?: string; privateKey: string; publicKey: string; + routeThroughXray?: boolean; s1: number; s2: number; s3: number; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index a54af8da7..ac0625d3a 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -688,6 +688,7 @@ export const ServerSettingsSchema = z.object({ primaryDns: z.string().optional(), privateKey: z.string(), publicKey: z.string(), + routeThroughXray: z.boolean().optional(), s1: z.number().int(), s2: z.number().int(), s3: z.number().int(), diff --git a/frontend/src/lib/xray/inbound-defaults.ts b/frontend/src/lib/xray/inbound-defaults.ts index 0c5fabc0b..6fb308cbe 100644 --- a/frontend/src/lib/xray/inbound-defaults.ts +++ b/frontend/src/lib/xray/inbound-defaults.ts @@ -298,6 +298,7 @@ export function createDefaultAmneziawgInboundSettings(): AmneziawgInboundSetting ipv6Enabled: false, ipv6Subnet: '', ipv6ExternalInterface: '', + routeThroughXray: false, jc: 5, jmin: 10, jmax: 50, diff --git a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx index 76da588e6..60680ccd7 100644 --- a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx +++ b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx @@ -68,6 +68,14 @@ export default function AmneziawgFields({ awgPubKey, regenInboundAwg, regenInbou > + + +