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 <noreply@anthropic.com>
This commit is contained in:
Kuzz007
2026-07-26 00:39:32 +03:00
parent c41f97cf86
commit 71dc453970
28 changed files with 257 additions and 85 deletions
+1
View File
@@ -666,6 +666,7 @@ export const EXAMPLES: Record<string, unknown> = {
"primaryDns": "",
"privateKey": "",
"publicKey": "",
"routeThroughXray": false,
"s1": 0,
"s2": 0,
"s3": 0,
+4
View File
@@ -2836,6 +2836,10 @@ export const SCHEMAS: Record<string, unknown> = {
"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"
},
+1
View File
@@ -649,6 +649,7 @@ export interface ServerSettings {
primaryDns?: string;
privateKey: string;
publicKey: string;
routeThroughXray?: boolean;
s1: number;
s2: number;
s3: number;
+1
View File
@@ -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(),
@@ -298,6 +298,7 @@ export function createDefaultAmneziawgInboundSettings(): AmneziawgInboundSetting
ipv6Enabled: false,
ipv6Subnet: '',
ipv6ExternalInterface: '',
routeThroughXray: false,
jc: 5,
jmin: 10,
jmax: 50,
@@ -68,6 +68,14 @@ export default function AmneziawgFields({ awgPubKey, regenInboundAwg, regenInbou
>
<Input placeholder="eth0" />
</FormField>
<FormField
name={['settings', 'server', 'routeThroughXray']}
label={t('pages.xray.amneziawg.routeThroughXray')}
extra={t('pages.xray.amneziawg.routeThroughXrayHint')}
valueProp="checked"
>
<Switch />
</FormField>
<Form.Item label={t('pages.xray.amneziawg.obfuscation')}>
<Button icon={<ReloadOutlined />} onClick={regenInboundAwgObfuscation}>
{t('pages.xray.amneziawg.regenerateObfuscation')}
@@ -52,6 +52,7 @@ export const AmneziawgServerSchema = z.object({
ipv6Enabled: z.boolean().default(false),
ipv6Subnet: z.string().default(''),
ipv6ExternalInterface: z.string().default(''),
routeThroughXray: z.boolean().default(false),
jc: z.number().int().min(0).default(5),
jmin: z.number().int().min(0).default(10),
jmax: z.number().int().min(0).default(50),