From 5617cdcf3192f78e1cb1c2872af285c39672d366 Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Sat, 25 Jul 2026 10:20:52 +0300 Subject: [PATCH] feat(amneziawg): add a button to randomize obfuscation parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the existing key-regenerate button next to the private key field. Client-side randomization matches the ranges/constraints of GenerateObfuscation20's "default" preset (internal/amneziawg/params.go) closely enough for a form suggestion — the user can still hand-edit any field afterward. Co-Authored-By: Claude Sonnet 5 --- .../pages/inbounds/form/InboundFormModal.tsx | 60 ++++++++++++++++++- .../inbounds/form/protocols/amneziawg.tsx | 8 ++- internal/web/translation/en-US.json | 2 + internal/web/translation/ru-RU.json | 2 + 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index 17110cee4..4c65c797b 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -332,6 +332,58 @@ export default function InboundFormModal({ setV('settings.server.publicKey', kp.publicKey); }; + // Randomizes the AmneziaWG 2.0 obfuscation set client-side, mirroring the + // ranges/constraints of the Go backend's amneziawg.GenerateObfuscation20 + // "default" preset (internal/amneziawg/params.go) closely enough for a form + // suggestion — exact parity isn't required since the user can still edit + // any field afterward, and a fresh, non-crypto-grade random value here is + // no weaker than what the backend would have generated on first save. + const regenInboundAwgObfuscation = () => { + const randInt = (min: number, max: number) => min + Math.floor(Math.random() * (max - min + 1)); + + const jc = randInt(3, 6); + const jmin = randInt(40, 89); + const jmax = jmin + randInt(50, 250); + + const s1 = randInt(15, 150); + let s2 = randInt(15, 150); + while (s1 + 56 === s2) { + s2 = randInt(15, 150); + } + const s3 = randInt(8, 55); + const s4 = randInt(4, 27); + + // Four non-overlapping "low-high" ranges for H1-H4: split the space into + // four bands and take a random sub-range from each (>= 1000 wide, low + // bound >= 5 since 1-4 are reserved for vanilla WireGuard message types). + const hMax = 2147483647; + const hMinWidth = 1000; + const lo = 5; + const bandSize = Math.floor((hMax - lo + 1) / 4); + const hRanges = Array.from({ length: 4 }, (_, i) => { + const bandLo = lo + i * bandSize; + const bandHi = bandLo + bandSize - 1; + const start = randInt(bandLo, bandHi - hMinWidth - 1); + const end = randInt(start + hMinWidth, bandHi - 1); + return `${start}-${end}`; + }); + + const i1 = ``; + + setV('settings.server.jc', jc); + setV('settings.server.jmin', jmin); + setV('settings.server.jmax', jmax); + setV('settings.server.s1', s1); + setV('settings.server.s2', s2); + setV('settings.server.s3', s3); + setV('settings.server.s4', s4); + setV('settings.server.h1', hRanges[0]); + setV('settings.server.h2', hRanges[1]); + setV('settings.server.h3', hRanges[2]); + setV('settings.server.h4', hRanges[3]); + setV('settings.server.i1', i1); + }; + const matchesVlessAuth = ( block: { id?: string; label?: string } | undefined | null, authId: string, @@ -676,7 +728,13 @@ export default function InboundFormModal({ <> {protocol === Protocols.WIREGUARD && } - {protocol === Protocols.AMNEZIAWG && } + {protocol === Protocols.AMNEZIAWG && ( + + )} {protocol === Protocols.TUN && } diff --git a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx index 2c780a8c9..b0481e8cf 100644 --- a/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx +++ b/frontend/src/pages/inbounds/form/protocols/amneziawg.tsx @@ -7,9 +7,10 @@ import { FormField } from '@/components/form/rhf'; interface AmneziawgFieldsProps { awgPubKey: string; regenInboundAwg: () => void; + regenInboundAwgObfuscation: () => void; } -export default function AmneziawgFields({ awgPubKey, regenInboundAwg }: AmneziawgFieldsProps) { +export default function AmneziawgFields({ awgPubKey, regenInboundAwg, regenInboundAwgObfuscation }: AmneziawgFieldsProps) { const { t } = useTranslation(); return ( <> @@ -46,6 +47,11 @@ export default function AmneziawgFields({ awgPubKey, regenInboundAwg }: Amneziaw > + + + diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index aeb8d806d..a865c758d 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -1933,6 +1933,8 @@ "secondaryDns": "Secondary DNS", "externalInterface": "External Interface", "externalInterfaceHint": "Host NIC for NAT (PostUp/PostDown). Leave empty to auto-detect.", + "obfuscation": "Obfuscation parameters", + "regenerateObfuscation": "Regenerate", "jc": "Jc (junk packet count)", "jmin": "Jmin (junk packet min size)", "jmax": "Jmax (junk packet max size)", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 7f2a7f5e2..382be0001 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -1816,6 +1816,8 @@ "secondaryDns": "Резервный DNS", "externalInterface": "Внешний интерфейс", "externalInterfaceHint": "Сетевой интерфейс хоста для NAT (PostUp/PostDown). Оставьте пустым для автоопределения.", + "obfuscation": "Параметры обфускации", + "regenerateObfuscation": "Сгенерировать заново", "jc": "Jc (кол-во мусорных пакетов)", "jmin": "Jmin (мин. размер мусорного пакета)", "jmax": "Jmax (макс. размер мусорного пакета)",