Стабильная работа туннеля: anti-RST, --fix-gro, QUIC-блок, автозагрузка
Проверено на живом стенде: 0% потерь на 10.66.66.1 и 8.8.8.8, выход через сервер (curl -> 95.174.102.77). Транспорт (устранение разрывов faketcp): - anti-RST вписан в конфиги вместо хрупкого -a+flush: клиент — первой строкой chain input (ip saddr 95.174.102.77 tcp sport 25565 drop), сервер — в таблице wgfwd (ip daddr 192.168.31.163 tcp dport 25565 drop, переживает ufw reload). Причина: RST от ядра убивал CGNAT-маппинг оператора -> direction timeout и смена порта. - --fix-gro на обоих концах: чинит дроп GRO «супер-пакетов» (huge packet > max_data_len). Клиент: - nftables.conf: flush ruleset (убирает дубли), reject QUIC/UDP 443 (откат браузера на TCP). - udp2raw-client.service: After=nftables.service (anti-RST применяется до старта udp2raw). Автозагрузка/порядок (обе службы udp2raw): - After=network-online.target, Before=wg-quick@wg0.service. README: раздел 5 «Известные нюансы» (anti-RST vs flush, CGNAT/RST, GRO), сниппеты и таблицы приведены в соответствие.
This commit is contained in:
40
README.md
40
README.md
@@ -1,6 +1,6 @@
|
||||
# Схема WireGuard + udp2raw
|
||||
|
||||
Проект содержит конфигурации для развёртывания отказоустойчивого VPN-туннеля между **удалённым сервером (Ubuntu 24.04)** и **Raspberry Pi 4**.
|
||||
Проект содержит конфигурации для развёртывания VPN-туннеля между **удалённым сервером (Ubuntu 24.04)** и **Raspberry Pi 4**.
|
||||
|
||||
Решение маскирует UDP-трафик WireGuard под обычные TCP-пакеты (режим `faketcp`), что позволяет обходить ограничения провайдеров, блокирующих или урезающих UDP на интерфейсе `usbnet`.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
- Downstream: `wlan0 = 192.168.77.0/24` — трафик клиентов уходит в интернет через туннель.
|
||||
- Прочие интерфейсы (`eth0 10.1.1.33`, `eth1 192.168.2.10`) в схеме VPN не участвуют.
|
||||
|
||||
### ⚠️ Обязательные предусловия окружения
|
||||
### Обязательные предусловия окружения
|
||||
|
||||
| # | Требование | Где | Проверка |
|
||||
|---|---|---|---|
|
||||
@@ -113,6 +113,9 @@ PostUp = nft add chain ip wgfwd postrouting { type nat hook postrouting priority
|
||||
PostUp = nft add rule ip wgfwd postrouting ip saddr 10.66.66.0/24 oifname "enp2s0" masquerade
|
||||
PostUp = nft add chain ip wgfwd mangle { type filter hook forward priority -150 \; policy accept \; }
|
||||
PostUp = nft add rule ip wgfwd mangle oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
|
||||
# udp2raw anti-RST (дубль к флагу -a, переживает ufw reload). Матч по своему IP+порту.
|
||||
PostUp = nft add chain ip wgfwd prerst { type filter hook input priority -300 \; policy accept \; }
|
||||
PostUp = nft add rule ip wgfwd prerst ip daddr 192.168.31.163 tcp dport 25565 drop
|
||||
PostDown = nft delete table ip wgfwd
|
||||
|
||||
[Peer]
|
||||
@@ -131,7 +134,7 @@ After=network.target
|
||||
Type=simple
|
||||
User=root
|
||||
# Слушать КОНКРЕТНЫЙ IP 192.168.31.163 (на 0.0.0.0 — bind fail из-за docker-бриджей)
|
||||
ExecStart=/usr/local/bin/udp2raw -s -l 192.168.31.163:25565 -r 127.0.0.1:51820 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp -a
|
||||
ExecStart=/usr/local/bin/udp2raw -s -l 192.168.31.163:25565 -r 127.0.0.1:51820 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp --fix-gro -a
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
@@ -186,7 +189,11 @@ sudo nft -f /etc/nftables.conf # применяется с flush (без д
|
||||
Ключевые строки, которые должны присутствовать:
|
||||
|
||||
```nft
|
||||
# table inet filter, chain input — ПЕРВЫМ правилом (выше ct-accept):
|
||||
ip saddr 95.174.102.77 tcp sport 25565 drop # udp2raw anti-RST
|
||||
|
||||
# table inet filter, chain forward:
|
||||
iifname "wlan0" oifname "wg0" udp dport 443 reject # блок QUIC → откат на TCP
|
||||
iifname "wlan0" oifname "wg0" accept
|
||||
iifname "wg0" oifname "wlan0" ct state established,related accept
|
||||
iifname "wlan0" oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu # MSS-clamp
|
||||
@@ -195,7 +202,7 @@ iifname "wlan0" oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
|
||||
ip saddr 192.168.77.0/24 oifname "wg0" masquerade
|
||||
```
|
||||
|
||||
> `nft -f` применяет файл с очисткой (flush) — это важно, чтобы не накапливались дубли правил при повторном запуске.
|
||||
> **`flush ruleset` + anti-RST.** Файл начинается с `flush ruleset` (убирает дубли при повторном `nft -f`). Но flush сносит и правило, которое udp2raw ставит флагом `-a` (drop входящего faketcp, иначе ядро шлёт RST и туннель рвётся при живом хендшейке). Поэтому anti-RST вписан прямо в `chain input` **первой строкой** — тогда `nft -f` атомарно восстанавливает его, и порядок применения перестаёт быть хрупким.
|
||||
|
||||
### Шаг 3.4. Конфигурации
|
||||
|
||||
@@ -229,7 +236,7 @@ After=network.target
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/udp2raw -c -l 127.0.0.1:51821 -r 95.174.102.77:25565 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp -a
|
||||
ExecStart=/usr/local/bin/udp2raw -c -l 127.0.0.1:51821 -r 95.174.102.77:25565 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp --fix-gro -a
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
@@ -263,18 +270,19 @@ sudo wg show
|
||||
|
||||
## 5. Известные нюансы и тюнинг
|
||||
|
||||
- **Автозагрузка + порядок.** Для боевого режима на обоих концах: `sudo systemctl enable udp2raw-<role> wg-quick@wg0`. Чтобы после ребута WG стартовал строго за udp2raw, добавить drop-in `sudo systemctl edit wg-quick@wg0`:
|
||||
```ini
|
||||
[Unit]
|
||||
After=udp2raw-client.service
|
||||
Wants=udp2raw-client.service
|
||||
- **⚠️ anti-RST vs `flush ruleset` (главная ловушка).** udp2raw в faketcp работает через AF_PACKET, а TCP-стек ядра видит «левые» пакеты на транспортном порту и шлёт **RST**. На мобильном канале это фатально вдвойне: RST уходит через CGNAT оператора → оператор считает TCP-поток закрытым и **убивает NAT-трансляцию** → пакеты клиент↔сервер начинают дропаться → udp2raw ловит `client-->server direction timeout` и переустанавливает faketcp **с новым source-портом** (виден в логе: `using port 14050`, `47011`…). Итог: туннель «поднят» (хендшейк WG есть), но данные идут пачками с 30–100% потерь, у выживших идеальный RTT. Флаг `-a` гасит это drop-правилом при старте udp2raw — **но `flush ruleset` его сносит**, и любой `nft -f` на живом туннеле убивает связь. Поэтому anti-RST вписан прямо в конфиги (идемпотентно к flush):
|
||||
- клиент — первой строкой `chain input`: `ip saddr 95.174.102.77 tcp sport 25565 drop`;
|
||||
- сервер — в таблице `wgfwd` (PostUp): `ip daddr 192.168.31.163 tcp dport 25565 drop` (переживает `ufw reload`).
|
||||
|
||||
Флаг `-a` в службах оставлен как подстраховка (закрывает окно до применения nft-правил). Порядок на клиенте: `nftables.service` → `udp2raw-client` (задано `After=nftables.service`; убедитесь, что `nftables.service` включён: `systemctl is-enabled nftables`).
|
||||
- **GRO / «huge packet ... dropped».** Если в логе udp2raw мелькает `huge packet, data_len NNNN > 1800(max_data_len) dropped` — стек склеивает входящие faketcp-сегменты в «супер-пакеты» больше `max_data_len`, и udp2raw их роняет (потери на крупном трафике). Лечится флагом `--fix-gro` в обеих службах (уже добавлен в `.service`). Альтернатива — выключить GRO на аплинке: `sudo ethtool -K usbnet gro off` (на сервере — `enp2s0`).
|
||||
- **Автозагрузка + порядок.** Порядок уже зашит в юниты udp2raw (`Before=wg-quick@wg0.service` + `After=network-online.target nftables.service`), отдельный drop-in не нужен. Для боевого режима достаточно включить обе службы на каждом конце:
|
||||
```bash
|
||||
sudo systemctl enable udp2raw-server wg-quick@wg0 # на сервере
|
||||
sudo systemctl enable udp2raw-client wg-quick@wg0 # на клиенте
|
||||
```
|
||||
(на сервере — `udp2raw-server.service`).
|
||||
После ребута udp2raw гарантированно поднимется раньше туннеля.
|
||||
- **«Не все сайты открываются» при рабочем туннеле** — почти всегда MTU/PMTUD:
|
||||
- Многие сайты используют HTTP/3 (QUIC поверх UDP 443), который **не подчиняется MSS-clamp**. Лечится либо снижением MTU туннеля (пробовать `1200 → 1160 → 1120` на обоих концах симметрично), либо блокировкой QUIC, чтобы браузер откатывался на TCP:
|
||||
```nft
|
||||
# в chain forward клиента, до accept-правил wg0:
|
||||
iifname "wlan0" oifname "wg0" udp dport 443 reject
|
||||
```
|
||||
- Многие сайты используют HTTP/3 (QUIC поверх UDP 443), который **не подчиняется MSS-clamp**. В `client/nftables.conf` уже добавлено правило `iifname "wlan0" oifname "wg0" udp dport 443 reject` — браузер откатывается на TCP/HTTPS (который зажат MSS-clamp'ом и пролезает). Если этого мало — симметрично снижать MTU туннеля (`1200 → 1160 → 1120` на обоих концах).
|
||||
- Проверка pMTU напрямую: с клиента `ping -M do -s 1172 10.66.66.1` (1172+28=1200). Если не проходит — MTU занижаем.
|
||||
- **Дубли правил nftables.** Если `nft list ruleset` показывает удвоенные строки — файл применяли `nft -f` поверх без flush. Лечится одним `sudo nft -f /etc/nftables.conf` со свежего файла (он начинается с flush) или ребутом.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/usr/sbin/nft -f
|
||||
# RASPBERRY PI 4: /etc/nftables.conf
|
||||
# flush обязателен: без него повторный `nft -f` накапливает дубли правил.
|
||||
flush ruleset
|
||||
|
||||
table ip nat {
|
||||
chain postrouting {
|
||||
@@ -11,6 +14,15 @@ table ip nat {
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
|
||||
# udp2raw faketcp anti-RST (КРИТИЧНО, должно стоять ПЕРВЫМ — выше ct-accept):
|
||||
# обратный faketcp от сервера попадает под ct established и доходит до TCP-стека
|
||||
# ядра, а тот шлёт RST на «левый» TCP -> шторм RST -> туннель встаёт, но данные
|
||||
# не идут. Прячем эти пакеты от стека; udp2raw читает их через AF_PACKET (до
|
||||
# netfilter), поэтому drop ему не мешает. Аналог правила, которое ставит флаг -a.
|
||||
# Держим правило ЗДЕСЬ (а не через -a), чтобы flush ruleset его не стирал.
|
||||
ip saddr 95.174.102.77 tcp sport 25565 drop
|
||||
|
||||
ct state established,related accept
|
||||
iifname "lo" accept
|
||||
ip protocol icmp accept
|
||||
@@ -28,6 +40,11 @@ table inet filter {
|
||||
iifname "wlan0" oifname "usbnet" accept
|
||||
iifname "usbnet" oifname "wlan0" ct state established,related accept
|
||||
|
||||
# Блокируем QUIC/HTTP-3 (UDP 443) в туннель: он НЕ подчиняется MSS-clamp,
|
||||
# крупные датаграммы теряются при MTU 1200 и сайты «висят».
|
||||
# reject (а не drop) → браузер сразу откатывается на TCP/HTTPS. Ставить ДО accept.
|
||||
iifname "wlan0" oifname "wg0" udp dport 443 reject
|
||||
|
||||
iifname "wlan0" oifname "wg0" accept
|
||||
iifname "wg0" oifname "wlan0" ct state established,related accept
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
[Unit]
|
||||
Description=udp2raw Client (FakeTCP)
|
||||
After=network.target
|
||||
# network-online (а не network.target): нужен поднятый uplink usbnet до старта.
|
||||
Wants=network-online.target
|
||||
# nftables.service должен примениться ДО udp2raw: в /etc/nftables.conf лежит anti-RST
|
||||
# правило (ip saddr 95.174.102.77 tcp sport 25565 drop). Требует enabled nftables.service.
|
||||
After=network-online.target nftables.service
|
||||
# Порядок автозагрузки: udp2raw поднимается ДО туннеля WireGuard (Endpoint=127.0.0.1:51821).
|
||||
Before=wg-quick@wg0.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
# Слушает локальный UDP-порт 51821 и транслирует в TCP на внешний вход сервера 95.174.102.77:25565
|
||||
# ВАЖНО: Пароль в параметре -k должен строго совпадать с паролем на сервере!
|
||||
ExecStart=/usr/local/bin/udp2raw -c -l 127.0.0.1:51821 -r 95.174.102.77:25565 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp -a
|
||||
# --fix-gro: стек склеивает входящие faketcp-сегменты в «супер-пакеты» >1800,
|
||||
# udp2raw их дропает ("huge packet ... dropped") — флаг это чинит.
|
||||
ExecStart=/usr/local/bin/udp2raw -c -l 127.0.0.1:51821 -r 95.174.102.77:25565 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp --fix-gro -a
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
[Unit]
|
||||
Description=udp2raw Server (FakeTCP)
|
||||
After=network.target
|
||||
# network-online (а не network.target): нужен реально поднятый enp2s0 с адресом
|
||||
# 192.168.31.163, иначе bind на конкретный IP упадёт при загрузке.
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
# Порядок автозагрузки: udp2raw поднимается ДО туннеля WireGuard.
|
||||
Before=wg-quick@wg0.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -12,7 +17,8 @@ User=root
|
||||
# иначе из-за множества docker-бриджей udp2raw падает с "bind fail".
|
||||
# Если LAN-адрес сервера сменится — поправить здесь И в пробросе на роутере.
|
||||
# ВАЖНО #2: Замените "MY_CONFIDENTIAL_PASS" на ваш секретный пароль для udp2raw
|
||||
ExecStart=/usr/local/bin/udp2raw -s -l 192.168.31.163:25565 -r 127.0.0.1:51820 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp -a
|
||||
# --fix-gro: чинит дроп «супер-пакетов» GRO (huge packet > max_data_len). Ставим на обоих концах.
|
||||
ExecStart=/usr/local/bin/udp2raw -s -l 192.168.31.163:25565 -r 127.0.0.1:51820 -k "MY_CONFIDENTIAL_PASS" --raw-mode faketcp --fix-gro -a
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
|
||||
@@ -22,6 +22,13 @@ PostUp = nft add rule ip wgfwd postrouting ip saddr 10.66.66.0/24 oifname "enp2s
|
||||
# чтобы large-TCP не «залипал» из-за MTU 1200 и заблокированного PMTUD.
|
||||
PostUp = nft add chain ip wgfwd mangle { type filter hook forward priority -150 \; policy accept \; }
|
||||
PostUp = nft add rule ip wgfwd mangle oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
|
||||
# udp2raw faketcp anti-RST (дубль к флагу -a): прячем входящий транспорт от TCP-стека,
|
||||
# иначе ядро шлёт RST на faketcp -> туннель рвётся. Матч по СВОЕМУ IP+порту (стабильно,
|
||||
# не зависит от динамического мобильного IP клиента). Приоритет -300 = раньше ufw/docker.
|
||||
# udp2raw читает через AF_PACKET (до netfilter), так что drop ему не мешает.
|
||||
# В своей таблице wgfwd — переживает ufw reload (в отличие от правила -a в table ip filter).
|
||||
PostUp = nft add chain ip wgfwd prerst { type filter hook input priority -300 \; policy accept \; }
|
||||
PostUp = nft add rule ip wgfwd prerst ip daddr 192.168.31.163 tcp dport 25565 drop
|
||||
PostDown = nft delete table ip wgfwd
|
||||
|
||||
[Peer]
|
||||
|
||||
Reference in New Issue
Block a user