Random I1 and MTU on install
I1 becomes a DNS query to a random popular domain (A/AAAA) instead of the shared icloud.com response every key carried. The format sticks to tags both the kernel module and amneziawg-go accept and is checked before awg1.conf is written: a bad I1 fails the whole awg-quick up. With KEEP_DATA=y the old I1 is kept, or generated if missing — the server never checks it, old keys keep their own. MTU is picked from 1320–1376 (AWG_MTU overrides) and kept on reinstall. awg-ctrl now reads MTU from awg1.conf for vpn:// keys instead of a constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
This commit is contained in:
+58
-6
@@ -151,7 +151,7 @@ NODE_KEY_FILE="$AWG_DIR/node.key"
|
|||||||
IFACE="awg1"
|
IFACE="awg1"
|
||||||
AWG_PORT="${AWG_PORT:-}" # UDP для клиентов; пусто — выбирает choose_awg_params
|
AWG_PORT="${AWG_PORT:-}" # UDP для клиентов; пусто — выбирает choose_awg_params
|
||||||
SUBNET="10.9"
|
SUBNET="10.9"
|
||||||
MTU="1376"
|
MTU="${AWG_MTU:-}" # MTU туннеля; пусто — выбирает choose_awg_params
|
||||||
|
|
||||||
# Единый бинарник: сервисы (ctrl/ui/agent) и CLI (start/stop/status/…). Команда awg-ctrl —
|
# Единый бинарник: сервисы (ctrl/ui/agent) и CLI (start/stop/status/…). Команда awg-ctrl —
|
||||||
# ссылка на него.
|
# ссылка на него.
|
||||||
@@ -357,7 +357,7 @@ fi
|
|||||||
DB_EXISTS="n"
|
DB_EXISTS="n"
|
||||||
[[ -f "$DB_FILE" ]] && DB_EXISTS="y"
|
[[ -f "$DB_FILE" ]] && DB_EXISTS="y"
|
||||||
|
|
||||||
# Параметры обфускации и UDP-порт AWG. На новой установке — случайные, у каждого сервера
|
# Параметры обфускации, UDP-порт и MTU AWG. На новой установке — случайные, у каждого сервера
|
||||||
# свои: известные всем значения по умолчанию — готовая сигнатура для блокировки. При KEEP_DATA=y
|
# свои: известные всем значения по умолчанию — готовая сигнатура для блокировки. При KEEP_DATA=y
|
||||||
# берутся из прежнего awg1.conf (choose_awg_params ниже): они зашиты во все выданные vpn://
|
# берутся из прежнего awg1.conf (choose_awg_params ниже): они зашиты во все выданные vpn://
|
||||||
# ключи, их смена сделала бы каждый ключ невалидным.
|
# ключи, их смена сделала бы каждый ключ невалидным.
|
||||||
@@ -371,10 +371,17 @@ DB_EXISTS="n"
|
|||||||
# - S4 добавляется к каждому пакету данных — держим маленьким, как прежние 14
|
# - S4 добавляется к каждому пакету данных — держим маленьким, как прежние 14
|
||||||
# - H1–H4: непересекающиеся диапазоны в [10^8, 2^31−1] — далеко от 1–4 (типы сообщений
|
# - H1–H4: непересекающиеся диапазоны в [10^8, 2^31−1] — далеко от 1–4 (типы сообщений
|
||||||
# WireGuard) и не выше прежних значений (некоторые клиенты разбирают их как int32)
|
# WireGuard) и не выше прежних значений (некоторые клиенты разбирают их как int32)
|
||||||
# - I1–I5 НЕ трогать: в vpn:// ключ уходят пустыми плейсхолдерами
|
# - I1 — пакет-маскировка, его шлёт инициатор handshake, принимающая сторона просто
|
||||||
|
# отбрасывает. Сервер его не сверяет: у каждого ключа может быть свой, поэтому при
|
||||||
|
# KEEP_DATA=y без I1 в конфиге его можно сгенерировать заново — старые ключи не сломаются.
|
||||||
|
# Формат обязан пройти И kernel-модуль (сервер), И amneziawg-go (клиенты) — см. AWG_I1_RE
|
||||||
|
# - I2–I5 НЕ трогать: в vpn:// ключ уходят пустыми плейсхолдерами
|
||||||
|
# - MTU 1320–1376: не выше прежнего (запас под S4, заголовки и IPv6 на пути), не ниже —
|
||||||
|
# лишние накладные расходы. Расхождение MTU сервера и клиента WireGuard переносит
|
||||||
# Прежние постоянные значения: ими собраны ключи установок до рандомизации — нужны, если
|
# Прежние постоянные значения: ими собраны ключи установок до рандомизации — нужны, если
|
||||||
# при KEEP_DATA=y awg1.conf потерялся.
|
# при KEEP_DATA=y awg1.conf потерялся.
|
||||||
LEGACY_AWG_PORT="47619"
|
LEGACY_AWG_PORT="47619"
|
||||||
|
LEGACY_MTU="1376"
|
||||||
LEGACY_JC=6; LEGACY_JMIN=10; LEGACY_JMAX=50
|
LEGACY_JC=6; LEGACY_JMIN=10; LEGACY_JMAX=50
|
||||||
LEGACY_S1=90; LEGACY_S2=45; LEGACY_S3=37; LEGACY_S4=14
|
LEGACY_S1=90; LEGACY_S2=45; LEGACY_S3=37; LEGACY_S4=14
|
||||||
LEGACY_H1="1224800044-2116730834"
|
LEGACY_H1="1224800044-2116730834"
|
||||||
@@ -401,6 +408,34 @@ gen_awg_params() {
|
|||||||
H1=${r[0]}; H2=${r[1]}; H3=${r[2]}; H4=${r[3]}
|
H1=${r[0]}; H2=${r[1]}; H3=${r[2]}; H4=${r[3]}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Синтаксис I1, который принимают обе реализации AmneziaWG:
|
||||||
|
# - только теги <b 0xHEX>, <r N>, <rc N>, <rd N>, <t>. <c> не знает amneziawg-go (клиент не
|
||||||
|
# применит ключ), <d>/<ds>/<dz> не знает kernel-модуль;
|
||||||
|
# - ровно ОДИН пробел между именем тега и аргументом: модуль делит по одиночному пробелу;
|
||||||
|
# - у <b> префикс 0x обязателен, hex — чётной длины;
|
||||||
|
# - текст вне <…> обе молча выкидывают — не пишем.
|
||||||
|
# Ошибка формата роняет весь `awg-quick up`, а модуль говорит лишь «Invalid argument».
|
||||||
|
AWG_I1_RE='^(<b 0x([0-9a-f]{2})+>|<(r|rc|rd) [1-9][0-9]{0,3}>|<t>)+$'
|
||||||
|
|
||||||
|
# Домены для I1: популярные, чтобы запрос выглядел обычным.
|
||||||
|
I1_DOMAINS=(google.com youtube.com yandex.ru vk.com mail.ru ok.ru apple.com icloud.com
|
||||||
|
microsoft.com cloudflare.com wikipedia.org github.com)
|
||||||
|
|
||||||
|
# I1 — DNS-запрос (его шлёт клиент, так что запрос правдоподобнее ответа): <r 2> — ID, новый
|
||||||
|
# в каждом пакете; флаги 0100 (стандартный, RD), 1 вопрос; случайный домен, A или AAAA, класс IN.
|
||||||
|
# 27–36 байт — меньше любого handshake-пакета (самый короткий, cookie, ≥ 79): по длине не путается.
|
||||||
|
gen_awg_i1() {
|
||||||
|
local d label qname="" qtype labels
|
||||||
|
d=${I1_DOMAINS[$(rand_int 0 $(( ${#I1_DOMAINS[@]} - 1 )))]}
|
||||||
|
[[ $(rand_int 0 1) == 1 ]] && d="www.$d"
|
||||||
|
IFS=. read -ra labels <<< "$d"
|
||||||
|
for label in "${labels[@]}"; do
|
||||||
|
qname+=$(printf '%02x' "${#label}")$(printf '%s' "$label" | od -An -tx1 | tr -d ' \n')
|
||||||
|
done
|
||||||
|
qtype=$([[ $(rand_int 0 2) == 0 ]] && echo 001c || echo 0001)
|
||||||
|
I1="<r 2><b 0x01000001000000000000${qname}00${qtype}0001>"
|
||||||
|
}
|
||||||
|
|
||||||
legacy_awg_params() {
|
legacy_awg_params() {
|
||||||
JC=$LEGACY_JC; JMIN=$LEGACY_JMIN; JMAX=$LEGACY_JMAX
|
JC=$LEGACY_JC; JMIN=$LEGACY_JMIN; JMAX=$LEGACY_JMAX
|
||||||
S1=$LEGACY_S1; S2=$LEGACY_S2; S3=$LEGACY_S3; S4=$LEGACY_S4
|
S1=$LEGACY_S1; S2=$LEGACY_S2; S3=$LEGACY_S3; S4=$LEGACY_S4
|
||||||
@@ -416,8 +451,9 @@ gen_awg_port() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Порт и обфускация: при KEEP_DATA=y — из прежнего awg1.conf (все J/S/H разом или ни одного:
|
# Порт, MTU и обфускация: при KEEP_DATA=y — из прежнего awg1.conf (все J/S/H разом или ни
|
||||||
# смешивать с новыми нельзя), иначе случайные. AWG_PORT из окружения важнее всего.
|
# одного: смешивать с новыми нельзя), иначе случайные. AWG_PORT и AWG_MTU из окружения
|
||||||
|
# важнее всего.
|
||||||
choose_awg_params() {
|
choose_awg_params() {
|
||||||
local k prev_port=""
|
local k prev_port=""
|
||||||
# || true: без файла sed падает, а с pipefail и set -e это молча роняло бы установку.
|
# || true: без файла sed падает, а с pipefail и set -e это молча роняло бы установку.
|
||||||
@@ -436,12 +472,27 @@ choose_awg_params() {
|
|||||||
prev_port=$(conf_val ListenPort)
|
prev_port=$(conf_val ListenPort)
|
||||||
[[ -z "$prev_port" ]] && prev_port=$(prev_val SERVER_PORT)
|
[[ -z "$prev_port" ]] && prev_port=$(prev_val SERVER_PORT)
|
||||||
AWG_PORT="${AWG_PORT:-${prev_port:-$LEGACY_AWG_PORT}}"
|
AWG_PORT="${AWG_PORT:-${prev_port:-$LEGACY_AWG_PORT}}"
|
||||||
|
MTU="${MTU:-$(conf_val MTU)}"
|
||||||
|
MTU="${MTU:-$LEGACY_MTU}"
|
||||||
|
I1=$(conf_val I1)
|
||||||
|
if [[ -n "$I1" && ! "$I1" =~ $AWG_I1_RE ]]; then
|
||||||
|
warn "I1 из $AWG_CONF не пройдёт парсер AmneziaWG — генерируем новый"
|
||||||
|
I1=""
|
||||||
|
fi
|
||||||
|
# Конфиги до рандомизации I1 не содержат: ключи брали общий из awg-ctrl. Новый
|
||||||
|
# безопасен — сервер I1 не сверяет, у старых ключей останется прежний.
|
||||||
|
[[ -z "$I1" ]] && gen_awg_i1
|
||||||
else
|
else
|
||||||
gen_awg_params
|
gen_awg_params
|
||||||
|
gen_awg_i1
|
||||||
AWG_PORT="${AWG_PORT:-$(gen_awg_port)}"
|
AWG_PORT="${AWG_PORT:-$(gen_awg_port)}"
|
||||||
|
MTU="${MTU:-$(rand_int 1320 1376)}"
|
||||||
fi
|
fi
|
||||||
[[ "$AWG_PORT" =~ ^[0-9]+$ && "$AWG_PORT" -gt 0 && "$AWG_PORT" -lt 65536 ]] \
|
[[ "$AWG_PORT" =~ ^[0-9]+$ && "$AWG_PORT" -gt 0 && "$AWG_PORT" -lt 65536 ]] \
|
||||||
|| fail "AWG_PORT «${AWG_PORT}» — не номер порта"
|
|| fail "AWG_PORT «${AWG_PORT}» — не номер порта"
|
||||||
|
[[ "$MTU" =~ ^[0-9]+$ && "$MTU" -ge 1280 && "$MTU" -le 1500 ]] \
|
||||||
|
|| fail "MTU «${MTU}» — нужно число 1280–1500"
|
||||||
|
[[ "$I1" =~ $AWG_I1_RE ]] || fail "I1 «${I1}» не пройдёт парсер AmneziaWG"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Поколение протокола и закрепление версии пакета — управляются переменными
|
# Поколение протокола и закрепление версии пакета — управляются переменными
|
||||||
@@ -842,7 +893,7 @@ echo -e " ${DIM}Роль ${NC} ${BLD}$ROLE_LABEL${NC}"
|
|||||||
echo -e " ${DIM}Адрес ${NC} $SERVER_IP ${DIM}· интерфейс $NET_IFACE${NC}"
|
echo -e " ${DIM}Адрес ${NC} $SERVER_IP ${DIM}· интерфейс $NET_IFACE${NC}"
|
||||||
if [[ "$ROLE" != "core" ]]; then
|
if [[ "$ROLE" != "core" ]]; then
|
||||||
echo -e " ${DIM}Имя сервера ${NC} $SERVER_NAME"
|
echo -e " ${DIM}Имя сервера ${NC} $SERVER_NAME"
|
||||||
echo -e " ${DIM}VPN ${NC} ${AWG_PORT}/udp"
|
echo -e " ${DIM}VPN ${NC} ${AWG_PORT}/udp ${DIM}· MTU ${MTU}${NC}"
|
||||||
fi
|
fi
|
||||||
if [[ "$ROLE" != "node" ]]; then
|
if [[ "$ROLE" != "node" ]]; then
|
||||||
echo -e " ${DIM}Панель ${NC} ${UI_PORT}/tcp ${DIM}· логин $UI_USER${NC}"
|
echo -e " ${DIM}Панель ${NC} ${UI_PORT}/tcp ${DIM}· логин $UI_USER${NC}"
|
||||||
@@ -1247,6 +1298,7 @@ H1 = ${H1}
|
|||||||
H2 = ${H2}
|
H2 = ${H2}
|
||||||
H3 = ${H3}
|
H3 = ${H3}
|
||||||
H4 = ${H4}
|
H4 = ${H4}
|
||||||
|
I1 = ${I1}
|
||||||
CONF
|
CONF
|
||||||
|
|
||||||
[[ -n "$AWG3_LINES" ]] && printf '%s\n' "$AWG3_LINES" >> "$AWG_CONF"
|
[[ -n "$AWG3_LINES" ]] && printf '%s\n' "$AWG3_LINES" >> "$AWG_CONF"
|
||||||
|
|||||||
@@ -344,8 +344,10 @@ func (s *Server) syncPeers() error {
|
|||||||
// vpn:// собирались бы на старых параметрах — например, на 2.0 уже после перехода на 3.1.
|
// vpn:// собирались бы на старых параметрах — например, на 2.0 уже после перехода на 3.1.
|
||||||
func (s *Server) reloadAwgParams() string {
|
func (s *Server) reloadAwgParams() string {
|
||||||
p := ReadParams(s.confPath(), func(m string) { s.log.Warn(m) })
|
p := ReadParams(s.confPath(), func(m string) { s.log.Warn(m) })
|
||||||
|
mtu := ReadMTU(s.confPath())
|
||||||
s.cfgMu.Lock()
|
s.cfgMu.Lock()
|
||||||
s.c.Params = p
|
s.c.Params = p
|
||||||
|
s.c.MTU = mtu
|
||||||
s.c.Gen = p.Gen()
|
s.c.Gen = p.Gen()
|
||||||
s.c.Keepalive = keepaliveByGen[s.c.Gen]
|
s.c.Keepalive = keepaliveByGen[s.c.Gen]
|
||||||
g := s.c.Gen
|
g := s.c.Gen
|
||||||
|
|||||||
@@ -70,6 +70,28 @@ var AWG31Defaults = [][2]string{
|
|||||||
{"MaxHandshakeAttempts", "15-20"},
|
{"MaxHandshakeAttempts", "15-20"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultMTU — MTU установок до рандомизации; запасное, если в awg1.conf его нет или оно странное.
|
||||||
|
const DefaultMTU = 1376
|
||||||
|
|
||||||
|
// ReadMTU читает MTU из [Interface] awg1.conf: install.sh выбирает его случайно, у каждого
|
||||||
|
// сервера своё, и клиенты получают его в vpn:// ключе.
|
||||||
|
func ReadMTU(confFile string) int {
|
||||||
|
raw, err := os.ReadFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return DefaultMTU
|
||||||
|
}
|
||||||
|
iface := pPeerSplit.Split(string(raw), 2)[0]
|
||||||
|
m := regexp.MustCompile(`(?m)^\s*MTU\s*=\s*(\d+)`).FindStringSubmatch(iface)
|
||||||
|
if m == nil {
|
||||||
|
return DefaultMTU
|
||||||
|
}
|
||||||
|
n, _ := strconv.Atoi(m[1])
|
||||||
|
if n < 1280 || n > 1500 {
|
||||||
|
return DefaultMTU
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// При заданном HeaderProtectionKey ни один из S1–S4 не может быть меньше 12.
|
// При заданном HeaderProtectionKey ни один из S1–S4 не может быть меньше 12.
|
||||||
const minSForHeaderProtection = 12
|
const minSForHeaderProtection = 12
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func New() (*Server, error) {
|
|||||||
module, tools := readVersions()
|
module, tools := readVersions()
|
||||||
s.c = Config{
|
s.c = Config{
|
||||||
ServerIP: ip, ServerPort: sp, ServerName: name,
|
ServerIP: ip, ServerPort: sp, ServerName: name,
|
||||||
DNS1: "1.1.1.1", DNS2: "1.0.0.1", MTU: 1376, Subnet: "10.9",
|
DNS1: "1.1.1.1", DNS2: "1.0.0.1", MTU: ReadMTU(s.confPath()), Subnet: "10.9",
|
||||||
Keepalive: keepaliveByGen[gen], Params: params, Gen: gen,
|
Keepalive: keepaliveByGen[gen], Params: params, Gen: gen,
|
||||||
AwgModule: module, AwgTools: tools,
|
AwgModule: module, AwgTools: tools,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user