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

14 KiB
Raw Blame History

Схема 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.77dev 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. Подготовка окружения

# 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. Диагностика

Проверять строго по цепочке снизу вверх:

  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.77dev 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:
    [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:
      # в 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) или ребутом.