- Переписан весь README с корректной markdown-разметкой (закрыты код-блоки, убраны литеральные "Bash"/"Ini, TOML"/"Code snippet", исправлены заголовки и съехавшие таблицы, устранена двойная нумерация раздела 4) - install -m 755 вместо cp+chmod, бинарник ФАЙЛОМ (частая ошибка на Pi) - Раздел 5 "Известные нюансы": автозагрузка с порядком запуска, диагностика "не все сайты" (QUIC/MTU), устранение дублей nftables
281 lines
14 KiB
Markdown
281 lines
14 KiB
Markdown
# Схема 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.17–172.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) или ребутом.
|