Files
vpn-raspberry/README.md
Maxim 888fd86dfa README: чистое форматирование + раздел тюнинга
- Переписан весь README с корректной markdown-разметкой (закрыты код-блоки,
  убраны литеральные "Bash"/"Ini, TOML"/"Code snippet", исправлены заголовки
  и съехавшие таблицы, устранена двойная нумерация раздела 4)
- install -m 755 вместо cp+chmod, бинарник ФАЙЛОМ (частая ошибка на Pi)
- Раздел 5 "Известные нюансы": автозагрузка с порядком запуска,
  диагностика "не все сайты" (QUIC/MTU), устранение дублей nftables
2026-07-07 16:24:57 +03:00

281 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Схема WireGuard + udp2raw
Проект содержит конфигурации для развёртывания отказоустойчивого VPN-туннеля между **удалённым сервером (Ubuntu 24.04)** и **Raspberry Pi 4**.
Решение маскирует UDP-трафик WireGuard под обычные TCP-пакеты (режим `faketcp`), что позволяет обходить ограничения провайдеров, блокирующих или урезающих UDP на интерфейсе `usbnet`.
> **Порт входа — `25565/tcp`, а не `443`.** Порт `443` на сервере занят nginx (reverse-proxy для Gitea `totmaxim.ru`); `udp2raw` в режиме `faketcp` через правило `-a` перехватил бы весь TCP на своём порту и поломал nginx. Поэтому транспорт вынесен на выделенный `25565/tcp`.
---
## 0. Справочные данные
### Архитектура и сетевые параметры
| Параметр | Значение |
|---|---|
| Публичный вход сервера | `95.174.102.77:25565` (TCP, `udp2raw`; `443` занят nginx/Gitea) |
| Внутренний порт WireGuard (сервер) | `127.0.0.1:51820` |
| Внутренний порт udp2raw (клиент RPi) | `127.0.0.1:51821` |
| Подсеть VPN | `10.66.66.0/24` (сервер `10.66.66.1`, клиент `10.66.66.2`) |
| MTU туннеля | `1200` (симметрично на обоих концах) |
### Фактическая топология (важно для маршрутизации)
**Сервер (Ubuntu 24.04) — за NAT, без прямого публичного IP:**
- Внешний интерфейс: `enp2s0 = 192.168.31.163/24`, шлюз `192.168.31.1`.
- `95.174.102.77` — WAN-адрес роутера `192.168.31.1`, а НЕ адрес самого сервера.
- На хосте работает Docker (бриджи `172.17172.21.0.0/16`, `docker0`) со своими правилами nftables/iptables — поэтому правила WG изолированы в отдельной таблице `wgfwd` и маскарадят **только** `10.66.66.0/24`.
**Клиент (Raspberry Pi 4) — роутер для локальной сети:**
- Uplink: `usbnet = 192.168.200.13/24`, шлюз и DNS `192.168.200.1` (мобильный канал; провайдер режет UDP → маскировка под TCP).
- Downstream: `wlan0 = 192.168.77.0/24` — трафик клиентов уходит в интернет через туннель.
- Прочие интерфейсы (`eth0 10.1.1.33`, `eth1 192.168.2.10`) в схеме VPN не участвуют.
### ⚠️ Обязательные предусловия окружения
| # | Требование | Где | Проверка |
|---|---|---|---|
| 1 | Проброс `TCP 25565 → 192.168.31.163:25565` на роутере `95.174.102.77` | Роутер сервера | с клиента: `nc -vz 95.174.102.77 25565` |
| 2 | `net.ipv4.ip_forward = 1` (закрепить в `/etc/sysctl.d/`) | Сервер + Клиент | `sysctl net.ipv4.ip_forward` |
| 3 | Порт `25565/tcp` на хосте свободен; `443` оставить за nginx | Сервер | `sudo ss -ltnp 'sport = :25565'` |
| 4 | Одинаковый пароль `-k` в обеих службах `udp2raw` | Сервер + Клиент | сверить `.service` |
| 5 | Маршрут `95.174.102.77/32 via 192.168.200.1 dev usbnet` при поднятом `wg0` | Клиент | `ip route get 95.174.102.77``dev usbnet`, НЕ `wg0` |
| 6 | Установлен пакет `iptables` (udp2raw добавляет правило `-a` только через него) | Клиент | `iptables -V` |
| 7 | udp2raw на сервере слушает `192.168.31.163`, а НЕ `0.0.0.0` (иначе `bind fail` из-за docker-бриджей) | Сервер | см. `-l` в `.service` |
> **Почему #5 критично:** на клиенте `AllowedIPs = 0.0.0.0/0`, поэтому `wg-quick` заворачивает весь трафик в `wg0`. Без закреплённого маршрута сам транспорт `udp2raw` (пакеты к `95.174.102.77:25565`) тоже уйдёт в `wg0` — получится **петля**, и туннель порвётся. Маршрут ставится автоматически через `PreUp` в `client/wg0.conf`.
### Ключи WireGuard
- **Сервер:** pub `dVIwnwxkjan/K278vzhOjp5A79N98j0O1zsYqT24qzs=` / priv `KMcPBI6LAY/NC1zFNYopsvNXqQ8w8ssluDj4yOh3skk=`
- **Клиент:** pub `WWFhoYfIgIhGOsvdJ/F9eWx7hPksrV6riiqo5lFPExI=` / priv `eKiGDY62/9pgFrwzoNIb7URRLPgEHC1W7zHwIIDYQVw=`
> ⚠️ Перед боевым запуском смените дефолтный пароль `MY_CONFIDENTIAL_PASS` в аргументе `-k` обеих служб `.service`. Он должен быть **одинаковым** на сервере и клиенте.
---
## 1. Релиз udp2raw
- Архив: `https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz`
- Версия: `20230206.0`
Из архива нужны только два файла:
1. `udp2raw_amd64` — на **сервер** (x86_64).
2. `udp2raw_arm64` (64-битная RPi OS, `aarch64`) или `udp2raw_arm_asm_aes` (32-битная, `armv7l`) — на **Raspberry Pi**. Уточнить: `uname -m`.
---
## 2. Развёртывание на СЕРВЕРЕ (Ubuntu)
### Шаг 2.0. Подготовка окружения
```bash
# 1) Проброс на роутере: TCP 25565 -> 192.168.31.163:25565 (проверить заранее)
# 2) Включаем и закрепляем форвардинг (wg-quick сам его НЕ включает):
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wg.conf
sudo sysctl --system
# 3) Порт 25565 на хосте должен быть свободен (443 остаётся за nginx):
sudo ss -ltnp 'sport = :25565'
```
### Шаг 2.1. Установка WireGuard
```bash
sudo apt update && sudo apt install wireguard -y
```
### Шаг 2.2. Установка бинарника udp2raw
```bash
mkdir -p ~/download && cd ~/download
wget https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz
tar -xvf udp2raw_binaries.tar.gz
sudo install -m 755 udp2raw_amd64 /usr/local/bin/udp2raw # именно ФАЙЛОМ, не каталогом
udp2raw -h >/dev/null && echo OK
```
### Шаг 2.3. Конфигурации
`/etc/wireguard/wg0.conf` (права `600`):
```ini
[Interface]
Address = 10.66.66.1/24
ListenPort = 51820
PrivateKey = KMcPBI6LAY/NC1zFNYopsvNXqQ8w8ssluDj4yOh3skk=
MTU = 1200
# Изолированная таблица wgfwd: masquerade ТОЛЬКО 10.66.66.0/24 (не конфликтует с Docker)
# + MSS-clamp на входе в туннель. PostDown сносит всё одной командой.
PostUp = nft add table ip wgfwd
PostUp = nft add chain ip wgfwd postrouting { type nat hook postrouting priority 100 \; policy accept \; }
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
PostDown = nft delete table ip wgfwd
[Peer]
PublicKey = WWFhoYfIgIhGOsvdJ/F9eWx7hPksrV6riiqo5lFPExI=
AllowedIPs = 10.66.66.2/32
```
`/etc/systemd/system/udp2raw-server.service` (не забыть заменить пароль в `-k`):
```ini
[Unit]
Description=udp2raw Server (FakeTCP)
After=network.target
[Service]
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
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
### Шаг 2.4. Запуск (сначала транспорт, потом туннель)
```bash
sudo systemctl daemon-reload
sudo systemctl start udp2raw-server # или enable --now — для автозагрузки
sudo systemctl start wg-quick@wg0
sudo wg show
sudo nft list table ip wgfwd
```
---
## 3. Развёртывание на КЛИЕНТЕ (Raspberry Pi 4)
### Шаг 3.1. Установка WireGuard и iptables
```bash
# iptables обязателен: на чистой nft-системе udp2raw не сможет добавить правило -a
sudo apt update && sudo apt install wireguard iptables -y
iptables -V # должно печатать версию (nf_tables)
```
### Шаг 3.2. Установка бинарника udp2raw
```bash
mkdir -p ~/download && cd ~/download
wget https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz
tar -xvf udp2raw_binaries.tar.gz
uname -m # aarch64 -> udp2raw_arm64 ; armv7l -> udp2raw_arm_asm_aes
# ВАЖНО: класть ФАЙЛОМ по пути /usr/local/bin/udp2raw (не каталогом!)
sudo install -m 755 udp2raw_arm64 /usr/local/bin/udp2raw
udp2raw -h >/dev/null && echo OK
```
### Шаг 3.3. Firewall (nftables)
Правила уже собраны в `client/nftables.conf` (полный ruleset малинки + MSS-clamp). Бэкап и применение:
```bash
sudo cp /etc/nftables.conf /etc/nftables.conf.bak.$(date +%F)
sudo cp client/nftables.conf /etc/nftables.conf
sudo nft -f /etc/nftables.conf # применяется с flush (без дублей)
```
Ключевые строки, которые должны присутствовать:
```nft
# table inet filter, chain forward:
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
# table ip nat, chain postrouting:
ip saddr 192.168.77.0/24 oifname "wg0" masquerade
```
> `nft -f` применяет файл с очисткой (flush) — это важно, чтобы не накапливались дубли правил при повторном запуске.
### Шаг 3.4. Конфигурации
`/etc/wireguard/wg0.conf` (права `600`; перед копированием сверить шлюз `ip route show default`):
```ini
[Interface]
Address = 10.66.66.2/24
PrivateKey = eKiGDY62/9pgFrwzoNIb7URRLPgEHC1W7zHwIIDYQVw=
MTU = 1200
# КРИТИЧНО (анти-петля): маршрут к серверу через реальный uplink ДО подъёма default в wg0.
# 192.168.200.1 — шлюз usbnet; при смене канала поправить здесь.
PreUp = ip route replace 95.174.102.77/32 via 192.168.200.1 dev usbnet
PostDown = ip route del 95.174.102.77/32 2>/dev/null || true
[Peer]
PublicKey = dVIwnwxkjan/K278vzhOjp5A79N98j0O1zsYqT24qzs=
Endpoint = 127.0.0.1:51821
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
```
`/etc/systemd/system/udp2raw-client.service` (пароль в `-k` — тот же, что на сервере):
```ini
[Unit]
Description=udp2raw Client (FakeTCP)
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
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
### Шаг 3.5. Запуск (сначала транспорт, потом туннель)
```bash
sudo systemctl daemon-reload
sudo systemctl start udp2raw-client # или enable --now — для автозагрузки
sudo systemctl start wg-quick@wg0
sudo wg show
```
---
## 4. Диагностика
Проверять строго по цепочке снизу вверх:
1. **Транспорт udp2raw:** `sudo systemctl status udp2raw-{server,client}` — в логах `now listening` / `client_ready`, без циклических перезапусков.
2. **Туннель WireGuard:** `sudo wg show` — есть `latest handshake: N seconds ago`, счётчики `transfer` растут.
3. **Отсутствие петли (клиент):** `ip route get 95.174.102.77``dev usbnet`, а не `dev wg0`. Иначе не отработал `PreUp`.
4. **Сетевой уровень:** с Pi `ping 10.66.66.1`. Идёт пинг до сервера — туннель жив.
5. **Выход через туннель:** с устройства за `wlan0` `curl -s https://ipinfo.io/ip` → должен вернуть `95.174.102.77` (адрес сети сервера).
6. **Доступность входа:** с клиента `nc -vz 95.174.102.77 25565` — порт открыт (проброс на роутере).
---
## 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
```
(на сервере — `udp2raw-server.service`).
- **«Не все сайты открываются» при рабочем туннеле** — почти всегда 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
```
- Проверка 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) или ребутом.