- Переписан весь README с корректной markdown-разметкой (закрыты код-блоки, убраны литеральные "Bash"/"Ini, TOML"/"Code snippet", исправлены заголовки и съехавшие таблицы, устранена двойная нумерация раздела 4) - install -m 755 вместо cp+chmod, бинарник ФАЙЛОМ (частая ошибка на Pi) - Раздел 5 "Известные нюансы": автозагрузка с порядком запуска, диагностика "не все сайты" (QUIC/MTU), устранение дублей nftables
14 KiB
Схема WireGuard + udp2raw
Проект содержит конфигурации для развёртывания отказоустойчивого VPN-туннеля между удалённым сервером (Ubuntu 24.04) и Raspberry Pi 4.
Решение маскирует UDP-трафик WireGuard под обычные TCP-пакеты (режим faketcp), что позволяет обходить ограничения провайдеров, блокирующих или урезающих UDP на интерфейсе usbnet.
Порт входа —
25565/tcp, а не443. Порт443на сервере занят nginx (reverse-proxy для Giteatotmaxim.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, шлюз и DNS192.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=/ privKMcPBI6LAY/NC1zFNYopsvNXqQ8w8ssluDj4yOh3skk= - Клиент: pub
WWFhoYfIgIhGOsvdJ/F9eWx7hPksrV6riiqo5lFPExI=/ priveKiGDY62/9pgFrwzoNIb7URRLPgEHC1W7zHwIIDYQVw=
⚠️ Перед боевым запуском смените дефолтный пароль
MY_CONFIDENTIAL_PASSв аргументе-kобеих служб.service. Он должен быть одинаковым на сервере и клиенте.
1. Релиз udp2raw
- Архив:
https://github.com/wangyu-/udp2raw/releases/download/20230206.0/udp2raw_binaries.tar.gz - Версия:
20230206.0
Из архива нужны только два файла:
udp2raw_amd64— на сервер (x86_64).udp2raw_arm64(64-битная RPi OS,aarch64) илиudp2raw_arm_asm_aes(32-битная,armv7l) — на Raspberry Pi. Уточнить:uname -m.
2. Развёртывание на СЕРВЕРЕ (Ubuntu)
Шаг 2.0. Подготовка окружения
# 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
sudo apt update && sudo apt install wireguard -y
Шаг 2.2. Установка бинарника udp2raw
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):
[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):
[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. Запуск (сначала транспорт, потом туннель)
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
# iptables обязателен: на чистой nft-системе udp2raw не сможет добавить правило -a
sudo apt update && sudo apt install wireguard iptables -y
iptables -V # должно печатать версию (nf_tables)
Шаг 3.2. Установка бинарника udp2raw
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). Бэкап и применение:
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 (без дублей)
Ключевые строки, которые должны присутствовать:
# 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):
[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 — тот же, что на сервере):
[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. Запуск (сначала транспорт, потом туннель)
sudo systemctl daemon-reload
sudo systemctl start udp2raw-client # или enable --now — для автозагрузки
sudo systemctl start wg-quick@wg0
sudo wg show
4. Диагностика
Проверять строго по цепочке снизу вверх:
- Транспорт udp2raw:
sudo systemctl status udp2raw-{server,client}— в логахnow listening/client_ready, без циклических перезапусков. - Туннель WireGuard:
sudo wg show— естьlatest handshake: N seconds ago, счётчикиtransferрастут. - Отсутствие петли (клиент):
ip route get 95.174.102.77→dev usbnet, а неdev wg0. Иначе не отработалPreUp. - Сетевой уровень: с Pi
ping 10.66.66.1. Идёт пинг до сервера — туннель жив. - Выход через туннель: с устройства за
wlan0curl -s https://ipinfo.io/ip→ должен вернуть95.174.102.77(адрес сети сервера). - Доступность входа: с клиента
nc -vz 95.174.102.77 25565— порт открыт (проброс на роутере).
5. Известные нюансы и тюнинг
- Автозагрузка + порядок. Для боевого режима на обоих концах:
sudo systemctl enable udp2raw-<role> wg-quick@wg0. Чтобы после ребута WG стартовал строго за udp2raw, добавить drop-insudo systemctl edit wg-quick@wg0:(на сервере —[Unit] After=udp2raw-client.service Wants=udp2raw-client.serviceudp2raw-server.service). - «Не все сайты открываются» при рабочем туннеле — почти всегда MTU/PMTUD:
- Многие сайты используют HTTP/3 (QUIC поверх UDP 443), который не подчиняется MSS-clamp. Лечится либо снижением MTU туннеля (пробовать
1200 → 1160 → 1120на обоих концах симметрично), либо блокировкой QUIC, чтобы браузер откатывался на TCP:# в 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 занижаем.
- Многие сайты используют HTTP/3 (QUIC поверх UDP 443), который не подчиняется MSS-clamp. Лечится либо снижением MTU туннеля (пробовать
- Дубли правил nftables. Если
nft list rulesetпоказывает удвоенные строки — файл применялиnft -fповерх без flush. Лечится однимsudo nft -f /etc/nftables.confсо свежего файла (он начинается с flush) или ребутом.