Files
vpn-raspberry/README.md
Maxim 5ab5ad2dce Транспорт udp2raw протестирован: bind к 192.168.31.163 + iptables на клиенте
По результатам дымового теста (faketcp handshake прошёл, server_ready/client_ready):
- server udp2raw: -l 0.0.0.0 -> 192.168.31.163 (на 0.0.0.0 bind fail из-за docker-бриджей)
- README: iptables добавлен в предусловия и шаг установки клиента
  (на чистой nft-системе udp2raw не может добавить правило -a)
- README: примечание про установку бинарника файлом и выбор ARM-архитектуры,
  таблица предусловий дополнена (строки iptables и bind-IP)
2026-07-07 14:19:03 +03:00

287 lines
16 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.

Файл документации (README.md)
# Схема 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`)
### Фактическая топология (важно для маршрутизации)
**Сервер (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 (Gitea) | Сервер | `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` только через него) | Клиент (чистый nft) | `iptables -V` |
| 7 | udp2raw на сервере слушает `192.168.31.163`, а НЕ `0.0.0.0` (иначе `bind fail` из-за docker-бриджей) | Сервер | см. `-l` в `.service` |
> **Заметка по портам/интерфейсам, проверено на стенде:** faketcp-рукопожатие успешно проходит (сервер `server_ready`, клиент `client_ready`). Клиент виден серверу под мобильным публичным IP (напр. `95.174.126.11`). Привязка сервера к `0.0.0.0` даёт `bind fail` — обязательно указывать конкретный IP интерфейса.
> **Почему требование #5 критично:** на клиенте `AllowedIPs = 0.0.0.0/0`, поэтому `wg-quick` заворачивает весь трафик в `wg0`. Без закреплённого маршрута сам транспорт `udp2raw` (пакеты к `95.174.102.77:25565`) тоже уйдёт в `wg0` — получится **петля**, и туннель порвётся. Маршрут добавляется автоматически через `PreUp` в `client/wg0.conf`.
### Актуальные ключи WireGuard
* **Сервер (`95.174.102.77`):**
* Public Key: `dVIwnwxkjan/K278vzhOjp5A79N98j0O1zsYqT24qzs=`
* Private Key: `KMcPBI6LAY/NC1zFNYopsvNXqQ8w8ssluDj4yOh3skk=`
* **Клиент (Raspberry Pi 4):**
* Public Key: `WWFhoYfIgIhGOsvdJ/F9eWx7hPksrV6riiqo5lFPExI=`
* Private Key: `eKiGDY62/9pgFrwzoNIb7URRLPgEHC1W7zHwIIDYQVw=`
> ⚠️ **Важно:** Перед запуском служб измените дефолтный пароль `"MY_CONFIDENTIAL_PASS"` в аргументе `-k` в файлах `.service` на ваш собственный секретный пароль. Он должен быть одинаковым везде.
---
## 1. Спецификация релиза udp2raw
* **Ссылка для скачивания архива:** `https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz`
* **Версия:** 20230206.0
При распаковке архива (`tar -xvf`) используются только два конкретных файла:
1. `udp2raw_amd64` — устанавливается на **Сервер** (архитектура x86_64).
2. `udp2raw_arm_asm_aes` — устанавливается на **Raspberry Pi 4** (оптимизированный ARM-бинарник).
---
## 2. Пошаговое развертывание на СЕРВЕРЕ (Ubuntu)
Выполняется в терминале сервера (`totserver@95.174.102.77`):
### Шаг 2.0. Подготовка окружения (проброс порта + форвардинг)
```bash
# 1) Убедитесь, что на роутере 95.174.102.77 проброшен 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 на хосте свободен (udp2raw слушает 192.168.31.163:25565).
# Порт 443 ДОЛЖЕН оставаться за nginx (Gitea) — udp2raw его не трогает:
sudo ss -ltnp 'sport = :25565'
# ПРИМЕЧАНИЕ: udp2raw слушает КОНКРЕТНЫЙ IP 192.168.31.163, а НЕ 0.0.0.0.
# На 0.0.0.0 из-за множества docker-бриджей он падает с "bind fail" (проверено).
```
### Шаг 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](https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz)
tar -xvf udp2raw_binaries.tar.gz
# Копируем x86_64 бинарник в системный путь и даем права
sudo cp udp2raw_amd64 /usr/local/bin/udp2raw
sudo chmod +x /usr/local/bin/udp2raw
Шаг 2.3. Создание конфигурационных файлов
Создайте конфиг WireGuard: sudo nano /etc/whiteguard/wg0.conf (или /etc/wireguard/wg0.conf)
Ini, TOML
[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
Создайте службу демона udp2raw: sudo nano /etc/systemd/system/udp2raw-server.service
Ini, TOML
[Unit]
Description=udp2raw Server (FakeTCP)
After=network.target
[Service]
Type=simple
User=root
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 enable --now udp2raw-server
sudo systemctl enable --now wg-quick@wg0
3. Пошаговое развертывание на КЛИЕНТЕ (Raspberry Pi 4)
Выполняется в терминале малинки (user@raspberrypi):
Шаг 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
# Внимание: положить бинарник ФАЙЛОМ по пути /usr/local/bin/udp2raw (не каталогом!).
# Архитектура: aarch64 -> udp2raw_arm64, armv7l -> udp2raw_arm_asm_aes (см. `uname -m`).
Если малинка имеет доступ к интернету, скачайте архив прямо на неё:
Bash
mkdir -p ~/download && cd ~/download
wget [https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz](https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz)
tar -xvf udp2raw_binaries.tar.gz
# Переносим специализированный ARM-бинарник в системный путь
sudo cp udp2raw_arm_asm_aes /usr/local/bin/udp2raw
sudo chmod +x /usr/local/bin/udp2raw
Шаг 3.3. Настройка сетевого экрана (nftables)
Учитывая строгую политику policy drop на вашей Raspberry Pi, откройте текущие правила:
Bash
sudo nano /etc/nftables.conf
Добавьте/разрешите следующие строки в соответствующие таблицы:
В table inet filter -> chain input:
Code snippet
iifname "lo" accept
iifname "wg0" accept
В table inet filter -> chain forward:
Code snippet
iifname "wlan0" oifname "wg0" accept
iifname "wg0" oifname "wlan0" ct state established,related accept
# MSS-clamp: клиенты (MTU 1500) не знают про туннель 1200 -> иначе крупный TCP залипает
iifname "wlan0" oifname "wg0" tcp flags syn tcp option maxseg size set rt mtu
В table ip nat -> chain postrouting:
Code snippet
ip saddr 192.168.77.0/24 oifname "wg0" masquerade
Примените изменения: sudo nft -f /etc/nftables.conf
Шаг 3.4. Создание конфигурационных файлов
Создайте конфиг WireGuard: sudo nano /etc/wireguard/wg0.conf
Ini, TOML
[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
Создайте службу демона udp2raw: sudo nano /etc/systemd/system/udp2raw-client.service
Ini, TOML
[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 enable --now udp2raw-client
sudo systemctl enable --now wg-quick@wg0
4. Диагностика и проверка статуса
При возникновении неполадок проверяйте компоненты строго по цепочке от нижнего уровня к верхнему:
1. Проверка работоспособности FakeTCP (udp2raw)
На клиенте: sudo systemctl status udp2raw-client
На сервере: sudo systemctl status udp2raw-server
Критерий успеха: Строка состояния [ready] в логах сервиса и отсутствие циклических перезапусков.
2. Проверка туннеля WireGuard
На любом из устройств: sudo wg show
Критерий успеха: Наличие строчки latest handshake: X seconds ago (данные должны обновляться и не сбрасываться).
3. Проверка сетевого уровня (Пинг)
С Raspberry Pi запустите: ping 10.66.66.1
Если пинг идет, но интернета на устройствах за малинкой (wlan0) нет — перепроверяйте синтаксис правил в /etc/nftables.conf и masquerade на сервере.
4. Проверка отсутствия петли (при поднятом wg0)
На клиенте: `ip route get 95.174.102.77`
Критерий успеха: маршрут идёт через `dev usbnet` (шлюз 192.168.200.1), а НЕ через `dev wg0`.
Если видите `dev wg0` — не отработал `PreUp` из `wg0.conf`, транспорт закольцован.
5. Проверка выхода в интернет через туннель
С устройства за малинкой (192.168.77.0/24): `curl -s https://ifconfig.me`
Критерий успеха: возвращается публичный IP канала сервера (сети `192.168.31.1`), а не мобильного канала клиента.
6. Доступность публичного входа (проброс 25565)
С клиента: `nc -vz 95.174.102.77 25565` — порт должен быть открыт (проброс на роутере сервера).