NaïveProxy: установка полной каскадной цепочки: различия между версиями
Владимир (обсуждение | вклад) Новая страница: « = NaïveProxy: установка полной каскадной цепочки = Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:<pre> Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP →...» |
Владимир (обсуждение | вклад) мНет описания правки |
||
| (не показано 5 промежуточных версий этого же участника) | |||
| Строка 1: | Строка 1: | ||
= NaïveProxy: установка полной каскадной цепочки = | |||
= NaïveProxy: | == Что такое NaïveProxy и зачем он нужен == | ||
Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:<pre> | |||
=== Проблема: как работают системы блокировок === | |||
Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), которые применяются интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса, но и '''характер трафика'''. Даже если трафик зашифрован, по поведению соединения можно определить, что это не обычный браузер, а программа для обхода ограничений (VPN, Shadowsocks, обычный SOCKS5-прокси и т.д.). | |||
Принцип прост: DPI «смотрит» на то, как именно клиент общается с сервером: | |||
* сколько байт летит в каждую сторону, | |||
* с какими временны́ми интервалами, | |||
* какой у соединения «fingerprint» (цифровой отпечаток), | |||
* как выглядит TLS-рукопожатие. | |||
Обычный HTTPS-прокси легко выявляется, потому что программа-клиент общается иначе, чем реальный браузер. | |||
=== Решение: маскировка под браузер Chrome === | |||
NaïveProxy решает эту проблему '''принципиально''': клиентская часть (naive binary) притворяется настоящим браузером Chrome, который делает обычные HTTPS-запросы. Серверная часть (Caddy с плагином forwardproxy) притворяется обычным веб-сервером. | |||
Ключевая особенность — NaïveProxy использует '''встроенный сетевой стек Chromium'''. Это значит, что трафик неотличим от реального Chrome не только по содержимому, но и по поведению на уровне TCP/TLS: те же задержки, те же размеры пакетов, тот же TLS fingerprint. | |||
{| class="wikitable" | |||
|- | |||
! Протокол !! Как выглядит для DPI !! Устойчивость к блокировке | |||
|- | |||
| Обычный VPN (WireGuard, OpenVPN) || Характерный шаблон UDP/TCP || Низкая — легко заблокировать по fingerprint | |||
|- | |||
| Shadowsocks || Случайный зашифрованный поток || Средняя — выявляется по энтропии и поведению | |||
|- | |||
| VLESS / VMess (без маскировки) || TLS, но нестандартный fingerprint || Средняя — детектируется active probing | |||
|- | |||
| VLESS + REALITY || TLS с реальным сертификатом стороннего сайта || Высокая — но заметен при активном зондировании | |||
|- | |||
| '''NaïveProxy''' || '''Неотличим от Chrome HTTPS''' || '''Очень высокая — устойчив к DPI и активному зондированию''' | |||
|} | |||
=== Как работает NaïveProxy технически === | |||
NaïveProxy использует протокол HTTP/2 CONNECT для туннелирования трафика. Упрощённо: | |||
# Клиент (naive binary на вашем устройстве) открывает HTTPS-соединение с сервером — точь-в-точь как Chrome. | |||
# Внутри этого HTTPS-соединения по протоколу HTTP/2 CONNECT прокидывается туннель для вашего трафика. | |||
# Сервер (Caddy + forwardproxy) обрабатывает запросы как обычный веб-сервер, а туннельные запросы перенаправляет дальше. | |||
# Если кто-то «постучится» на сервер без правильных credentials — сервер ответит как обычный сайт ('''probe_resistance''' — защита от зондирования). | |||
=== Каскадная цепочка: зачем два сервера === | |||
В данном решении используется '''каскад из двух серверов''': Entry Node (точка входа, ближайший к клиентам) и Exit Node (точка выхода в интернет). Это сделано намеренно: | |||
* '''Снижение риска для клиента:''' клиенты подключаются только к ближайшему серверу-входу — подключение к локальному узлу менее заметно, чем прямое соединение с зарубежным сервером. | |||
* '''Гибкость маршрутизации:''' Entry Node может обслуживать разных клиентов по разным протоколам (VLESS, Trojan, NaïveProxy) и перенаправлять их на Exit Node. | |||
* '''Дополнительный слой анонимности:''' Exit Node выходит в интернет через Cloudflare WARP — конечный сайт видит IP Cloudflare, а не IP вашего сервера. | |||
---- | |||
== Схема конкретного решения == | |||
=== Компоненты инфраструктуры === | |||
{| class="wikitable" | |||
|- | |||
! Компонент !! Роль !! IP / Адрес !! ОС | |||
|- | |||
| Устройства домашней сети || Конечные пользователи || — || Любые | |||
|- | |||
| Роутер Xiaomi Redmi AX6000 || Прозрачный прокси для всей сети || — || OpenWrt 24.10 (aarch64) | |||
|- | |||
| RU сервер ('''Entry Node''') || Точка входа (Entry Node) || <code>YOUR_RU_SERVER_IP</code> / <code>your-naive-ru-domain.example.com</code> || Ubuntu 24.04 | |||
|- | |||
| EU сервер ('''Exit Node''') || Точка выхода (Exit Node) || <code>YOUR_EU_SERVER_IP</code> / <code>your-naive-eu-domain.example.com</code> || Ubuntu 24.04 | |||
|- | |||
| Cloudflare WARP || Финальный выход в интернет || IP Cloudflare || — | |||
|} | |||
=== Полная схема прохождения трафика === | |||
[[Файл:Generated image NaïveProxy Полная схема прохождения трафика.png|безрамки|739x739пкс]] | |||
=== Протоколы на каждом участке цепочки === | |||
{| class="wikitable" | |||
|- | |||
! Участок !! Протокол !! Шифрование !! Что видит наблюдатель | |||
|- | |||
| Устройство → Роутер || Обычный TCP/UDP || Нет (локальная сеть) || Локальный трафик | |||
|- | |||
| Роутер → RU Caddy || HTTPS HTTP/2 (NaïveProxy) || TLS 1.3 (Let's Encrypt) || Браузер Chrome обращается к сайту | |||
|- | |||
| RU naive → EU Caddy || HTTPS HTTP/2 (NaïveProxy) || TLS 1.3 (Let's Encrypt) || Браузер Chrome обращается к сайту | |||
|- | |||
| EU Caddy → 3x-ui inbound || SOCKS5 (localhost) || — (локально) || Только на EU сервере | |||
|- | |||
| EU 3x-ui → WARP || WireGuard (gVisor) || WireGuard || Зашифрованный WireGuard к Cloudflare | |||
|- | |||
| WARP → Интернет || Обычный TCP/UDP || — || IP Cloudflare | |||
|} | |||
=== Дополнительные каналы (параллельно NaïveProxy) === | |||
{| class="wikitable" | |||
|- | |||
! Канал !! Протокол !! Маршрут !! Статус | |||
|- | |||
| 3x-ui каскад (основной) || VLESS / REALITY || Клиенты → RU 3x-ui → EU 3x-ui → Интернет || ✅ Активен | |||
|- | |||
| NaïveProxy каскад (этот гайд) || HTTP/2 naïve || Роутер/ПК → RU Caddy → EU Caddy → WARP || ✅ Активен | |||
|- | |||
| MTProto (Telegram) || MTProto || Клиенты Telegram → EU :8443, :2443 || ✅ Активен | |||
|- | |||
| Hysteria2 || QUIC/UDP || → EU || ⏸ Конфиг есть, сервис не запущен | |||
|} | |||
---- | |||
Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. | |||
Трафик проходит полную цепочку: | |||
<pre> | |||
Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет | Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет | ||
</pre>На серверах предполагается, что '''3x-ui уже установлен и работает'''. | </pre> | ||
На серверах предполагается, что '''3x-ui уже установлен и работает'''. | |||
== Предварительные требования == | == Предварительные требования == | ||
| Строка 76: | Строка 193: | ||
== Часть 1: Настройка EU сервера (Exit Node) == | == Часть 1: Настройка EU сервера (Exit Node) == | ||
Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker. Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP. | |||
Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker. | |||
Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP. | |||
=== 1.1 Установка Docker === | === 1.1 Установка Docker === | ||
Выполни на EU сервере:<syntaxhighlight lang="bash"> | |||
Выполни на EU сервере: | |||
<syntaxhighlight lang="bash"> | |||
# Обновить пакеты | # Обновить пакеты | ||
apt-get update && apt-get upgrade -y | apt-get update && apt-get upgrade -y | ||
| Строка 106: | Строка 228: | ||
docker --version | docker --version | ||
docker compose version | docker compose version | ||
</syntaxhighlight>Ожидаемый вывод:<pre> | </syntaxhighlight> | ||
Ожидаемый вывод: | |||
<pre> | |||
Docker version 27.x.x, build ... | Docker version 27.x.x, build ... | ||
Docker Compose version v2.x.x | Docker Compose version v2.x.x | ||
| Строка 112: | Строка 237: | ||
=== 1.2 Создание структуры каталогов === | === 1.2 Создание структуры каталогов === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
mkdir -p /opt/caddy-naive/www | mkdir -p /opt/caddy-naive/www | ||
| Строка 118: | Строка 244: | ||
=== 1.3 Создание Dockerfile === | === 1.3 Создание Dockerfile === | ||
Caddy собирается с нестандартным плагином <code>forwardproxy</code> (форк klzgrad/forwardproxy@naive). Используется Go 1.25+ — обязательное требование для текущей версии Caddy.<syntaxhighlight lang="bash"> | |||
Caddy собирается с нестандартным плагином <code>forwardproxy</code> (форк klzgrad/forwardproxy@naive). | |||
Используется Go 1.25+ — обязательное требование для текущей версии Caddy. | |||
<syntaxhighlight lang="bash"> | |||
cat > /opt/caddy-naive/Dockerfile << 'EOF' | cat > /opt/caddy-naive/Dockerfile << 'EOF' | ||
FROM golang:1.25-alpine AS builder | FROM golang:1.25-alpine AS builder | ||
| Строка 138: | Строка 268: | ||
=== 1.4 Создание docker-compose.yml === | === 1.4 Создание docker-compose.yml === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/caddy-naive/docker-compose.yml << 'EOF' | cat > /opt/caddy-naive/docker-compose.yml << 'EOF' | ||
| Строка 146: | Строка 277: | ||
restart: unless-stopped | restart: unless-stopped | ||
network_mode: host | network_mode: host | ||
environment: | |||
- XDG_DATA_HOME=/data | |||
volumes: | volumes: | ||
- ./Caddyfile:/etc/caddy/Caddyfile:ro | - ./Caddyfile:/etc/caddy/Caddyfile:ro | ||
| Строка 158: | Строка 291: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
; Почему network_mode | ; Почему XDG_DATA_HOME=/data? | ||
: host? | : Кастомный образ Caddy (собранный из <code>debian:bookworm-slim</code>) не наследует переменные официального Docker образа Caddy. Без этой переменной Caddy хранит сертификаты и служебные данные в <code>/root/.local/share/caddy/</code> — внутри writable layer контейнера. При пересборке или пересоздании контейнера все сертификаты теряются, и Caddy запрашивает новые у Let's Encrypt (лимит: 5 сертификатов на домен в неделю). С <code>XDG_DATA_HOME=/data</code> данные пишутся в <code>/data/caddy/</code>, который примонтирован как named volume — и переживают любые пересборки. | ||
; Почему network_mode: host? | |||
: Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы: | : Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы: | ||
:* Caddy получал реальные клиентские IP для корректной работы probe_resistance | :* Caddy получал реальные клиентские IP для корректной работы probe_resistance | ||
| Строка 165: | Строка 300: | ||
=== 1.5 Создание заглушки для probe_resistance === | === 1.5 Создание заглушки для probe_resistance === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/caddy-naive/www/index.html << 'EOF' | cat > /opt/caddy-naive/www/index.html << 'EOF' | ||
| Строка 177: | Строка 313: | ||
: Функция <code>probe_resistance</code> в Caddy скрывает факт наличия прокси. | : Функция <code>probe_resistance</code> в Caddy скрывает факт наличия прокси. | ||
: При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси. | : При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси. | ||
: Это защищает от автоматических сканеров и активных проверок | : Это защищает от автоматических сканеров и активных DPI-проверок. | ||
=== 1.6 Создание Caddyfile === | === 1.6 Создание Caddyfile === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /opt/caddy-naive/Caddyfile << 'EOF' | cat > /opt/caddy-naive/Caddyfile << 'EOF' | ||
| Строка 202: | Строка 339: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
; Важные нюансы синтаксиса | ; Важные нюансы синтаксиса: | ||
: | |||
: * <code>:443, your-naive-eu-domain.example.com</code> — запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает | : * <code>:443, your-naive-eu-domain.example.com</code> — запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает | ||
: * <code>upstream socks5://127.0.0.1:24363</code> — перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет | : * <code>upstream socks5://127.0.0.1:24363</code> — перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет | ||
| Строка 209: | Строка 345: | ||
=== 1.7 Настройка Xray inbound для WARP (в 3x-ui) === | === 1.7 Настройка Xray inbound для WARP (в 3x-ui) === | ||
Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет. | Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет. | ||
| Строка 229: | Строка 366: | ||
# Это направит весь трафик от Caddy через Cloudflare WARP | # Это направит весь трафик от Caddy через Cloudflare WARP | ||
; Проверка WARP inbound | ; Проверка WARP inbound: | ||
: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me | curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me | ||
| Строка 237: | Строка 373: | ||
=== 1.8 Сборка и запуск === | === 1.8 Сборка и запуск === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /opt/caddy-naive | cd /opt/caddy-naive | ||
| Строка 248: | Строка 385: | ||
# Наблюдение за логами (получение сертификата занимает ~30 секунд) | # Наблюдение за логами (получение сертификата занимает ~30 секунд) | ||
docker compose logs -f | docker compose logs -f | ||
</syntaxhighlight>Ожидаемый вывод в логах:<pre> | </syntaxhighlight> | ||
Ожидаемый вывод в логах: | |||
<pre> | |||
... | ... | ||
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"} | {"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"} | ||
| Строка 255: | Строка 395: | ||
=== 1.9 Проверка EU сервера === | === 1.9 Проверка EU сервера === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси) | # Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси) | ||
| Строка 262: | Строка 403: | ||
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me | curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | ---- | ||
== Часть 2: Настройка RU сервера (Entry Node) == | == Часть 2: Настройка RU сервера (Entry Node) == | ||
Цель: на RU сервере поднять два компонента: | Цель: на RU сервере поднять два компонента: | ||
# '''Caddy''' (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon | # '''Caddy''' (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon | ||
# '''naive daemon''' (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy | # '''naive daemon''' (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy | ||
=== 2.1 Установка Docker === | === 2.1 Установка Docker === | ||
Те же команды, что и для EU сервера:<syntaxhighlight lang="bash"> | |||
Те же команды, что и для EU сервера: | |||
<syntaxhighlight lang="bash"> | |||
apt-get update && apt-get upgrade -y | apt-get update && apt-get upgrade -y | ||
apt-get install -y ca-certificates curl gnupg lsb-release | apt-get install -y ca-certificates curl gnupg lsb-release | ||
| Строка 293: | Строка 438: | ||
=== 2.2 Структура каталогов и файлы Docker === | === 2.2 Структура каталогов и файлы Docker === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
mkdir -p /opt/caddy-naive/www | mkdir -p /opt/caddy-naive/www | ||
cd /opt/caddy-naive | cd /opt/caddy-naive | ||
</syntaxhighlight>Dockerfile идентичен EU серверу:<syntaxhighlight lang="bash"> | </syntaxhighlight> | ||
Dockerfile идентичен EU серверу: | |||
<syntaxhighlight lang="bash"> | |||
cat > /opt/caddy-naive/Dockerfile << 'EOF' | cat > /opt/caddy-naive/Dockerfile << 'EOF' | ||
FROM golang:1.25-alpine AS builder | FROM golang:1.25-alpine AS builder | ||
| Строка 310: | Строка 460: | ||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] | CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] | ||
EOF | EOF | ||
</syntaxhighlight><syntaxhighlight lang="bash"> | </syntaxhighlight> | ||
<syntaxhighlight lang="bash"> | |||
cat > /opt/caddy-naive/docker-compose.yml << 'EOF' | cat > /opt/caddy-naive/docker-compose.yml << 'EOF' | ||
services: | services: | ||
| Строка 318: | Строка 470: | ||
restart: unless-stopped | restart: unless-stopped | ||
network_mode: host | network_mode: host | ||
environment: | |||
- XDG_DATA_HOME=/data | |||
volumes: | volumes: | ||
- ./Caddyfile:/etc/caddy/Caddyfile:ro | - ./Caddyfile:/etc/caddy/Caddyfile:ro | ||
| Строка 328: | Строка 482: | ||
caddy_logs: | caddy_logs: | ||
EOF | EOF | ||
</syntaxhighlight><syntaxhighlight lang="bash"> | </syntaxhighlight> | ||
<syntaxhighlight lang="bash"> | |||
cat > /opt/caddy-naive/www/index.html << 'EOF' | cat > /opt/caddy-naive/www/index.html << 'EOF' | ||
<!DOCTYPE html> | <!DOCTYPE html> | ||
| Строка 338: | Строка 494: | ||
=== 2.3 Создание Caddyfile RU сервера === | === 2.3 Создание Caddyfile RU сервера === | ||
Ключевое отличие от EU: директива <code>upstream socks5://127.0.0.1:10808</code>, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon.<syntaxhighlight lang="bash"> | |||
Ключевое отличие от EU: директива <code>upstream socks5://127.0.0.1:10808</code>, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon. | |||
<syntaxhighlight lang="bash"> | |||
cat > /opt/caddy-naive/Caddyfile << 'EOF' | cat > /opt/caddy-naive/Caddyfile << 'EOF' | ||
{ | { | ||
| Строка 364: | Строка 523: | ||
=== 2.4 Сборка и запуск Caddy === | === 2.4 Сборка и запуск Caddy === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cd /opt/caddy-naive | cd /opt/caddy-naive | ||
| Строка 369: | Строка 529: | ||
docker compose up -d | docker compose up -d | ||
docker compose logs -f | docker compose logs -f | ||
</syntaxhighlight>Ожидаемый вывод:<pre> | </syntaxhighlight> | ||
Ожидаемый вывод: | |||
<pre> | |||
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"} | {"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"} | ||
</pre> | </pre> | ||
| Строка 376: | Строка 539: | ||
==== 2.5.1 Определение архитектуры сервера ==== | ==== 2.5.1 Определение архитектуры сервера ==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
uname -m | uname -m | ||
| Строка 382: | Строка 546: | ||
==== 2.5.2 Скачивание бинарника naive ==== | ==== 2.5.2 Скачивание бинарника naive ==== | ||
Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases | Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases | ||
Найди архив для своей архитектуры. Для Ubuntu x86_64:<pre> | Найди архив для своей архитектуры. Для Ubuntu x86_64: | ||
<pre> | |||
naiveproxy-vXXX-linux-x86_64.tar.xz | naiveproxy-vXXX-linux-x86_64.tar.xz | ||
</pre><syntaxhighlight lang="bash"> | </pre> | ||
<syntaxhighlight lang="bash"> | |||
# Скачать (замени URL на актуальный из Releases) | # Скачать (замени URL на актуальный из Releases) | ||
cd /tmp | cd /tmp | ||
| Строка 405: | Строка 573: | ||
==== 2.5.3 Создание конфигурации naive daemon ==== | ==== 2.5.3 Создание конфигурации naive daemon ==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
mkdir -p /etc/naive | mkdir -p /etc/naive | ||
| Строка 417: | Строка 586: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
; Что происходит | ; Что происходит: | ||
:* <code>listen</code> — naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU) | :* <code>listen</code> — naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU) | ||
:* <code>proxy</code> — подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS) | :* <code>proxy</code> — подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS) | ||
==== 2.5.4 Создание systemd-сервиса ==== | ==== 2.5.4 Создание systemd-сервиса ==== | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /etc/systemd/system/naive.service << 'EOF' | cat > /etc/systemd/system/naive.service << 'EOF' | ||
| Строка 437: | Строка 607: | ||
WantedBy=multi-user.target | WantedBy=multi-user.target | ||
EOF | EOF | ||
</syntaxhighlight><syntaxhighlight lang="bash"> | </syntaxhighlight> | ||
<syntaxhighlight lang="bash"> | |||
# Активировать и запустить | # Активировать и запустить | ||
systemctl daemon-reload | systemctl daemon-reload | ||
| Строка 445: | Строка 617: | ||
# Проверить статус | # Проверить статус | ||
systemctl status naive | systemctl status naive | ||
</syntaxhighlight>Ожидаемый вывод:<pre> | </syntaxhighlight> | ||
Ожидаемый вывод: | |||
<pre> | |||
● naive.service - NaïveProxy client daemon | ● naive.service - NaïveProxy client daemon | ||
Loaded: loaded (/etc/systemd/system/naive.service; enabled) | Loaded: loaded (/etc/systemd/system/naive.service; enabled) | ||
| Строка 452: | Строка 627: | ||
=== 2.6 Проверка цепочки с RU сервера === | === 2.6 Проверка цепочки с RU сервера === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Тест HTTP через naive daemon → EU → WARP | # Тест HTTP через naive daemon → EU → WARP | ||
| Строка 461: | Строка 637: | ||
# Должен вернуть тот же результат | # Должен вернуть тот же результат | ||
</syntaxhighlight> | </syntaxhighlight> | ||
---- | ---- | ||
== Часть 3: Настройка OpenWrt роутера == | == Часть 3: Настройка OpenWrt роутера == | ||
Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy. | Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy. | ||
=== 3.1 Определение архитектуры роутера === | === 3.1 Определение архитектуры роутера === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
uname -m | uname -m | ||
# aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static | # aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static | ||
# или x86_64, mipsel, armv7l — зависит от модели роутера | # или x86_64, mipsel, armv7l — зависит от модели роутера | ||
</syntaxhighlight><syntaxhighlight lang="bash"> | </syntaxhighlight> | ||
<syntaxhighlight lang="bash"> | |||
# Подробнее об архитектуре | # Подробнее об архитектуре | ||
cat /etc/openwrt_release | grep ARCH | cat /etc/openwrt_release | grep ARCH | ||
| Строка 477: | Строка 658: | ||
=== 3.2 Скачивание бинарника naive для OpenWrt === | === 3.2 Скачивание бинарника naive для OpenWrt === | ||
На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив:<pre> | |||
На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив: | |||
<pre> | |||
naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz | naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz | ||
</pre>(замени архитектуру на свою)<syntaxhighlight lang="bash"> | </pre> | ||
(замени архитектуру на свою) | |||
<syntaxhighlight lang="bash"> | |||
# Создать каталог | # Создать каталог | ||
mkdir -p /etc/naive | mkdir -p /etc/naive | ||
| Строка 488: | Строка 674: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
; Важно | ; Важно: OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую. | ||
: OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую. | Используй pipe с xzcat: | ||
<syntaxhighlight lang="bash"> | |||
# Если xz не установлен: | # Если xz не установлен: | ||
opkg update && opkg install xz | opkg update && opkg install xz | ||
| Строка 507: | Строка 693: | ||
=== 3.3 Создание конфигурации === | === 3.3 Создание конфигурации === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /etc/naive/config.json << 'EOF' | cat > /etc/naive/config.json << 'EOF' | ||
| Строка 517: | Строка 704: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
; Чем этот конфиг отличается от конфига на RU сервере | ; Чем этот конфиг отличается от конфига на RU сервере: | ||
:* Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада | :* Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада | ||
:* Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его) | :* Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его) | ||
=== 3.4 Создание init-скрипта procd === | === 3.4 Создание init-скрипта procd === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
cat > /etc/init.d/naive << 'EOF' | cat > /etc/init.d/naive << 'EOF' | ||
| Строка 547: | Строка 735: | ||
=== 3.5 Запуск и автозапуск === | === 3.5 Запуск и автозапуск === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Включить автозапуск | # Включить автозапуск | ||
| Строка 559: | Строка 748: | ||
=== 3.6 Проверка === | === 3.6 Проверка === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Тест подключения через наивный прокси | # Тест подключения через наивный прокси | ||
| Строка 566: | Строка 756: | ||
=== 3.7 Настройка podkop === | === 3.7 Настройка podkop === | ||
В интерфейсе podkop (LuCI или CLI): | В интерфейсе podkop (LuCI или CLI): | ||
| Строка 575: | Строка 766: | ||
=== 3.8 Управление сервисом на роутере === | === 3.8 Управление сервисом на роутере === | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
|<code>/etc/init.d/naive start</code> | ! Команда !! Действие | ||
|Запустить | |- | ||
| <code>/etc/init.d/naive start</code> || Запустить | |||
|- | |- | ||
|<code>/etc/init.d/naive stop</code> | | <code>/etc/init.d/naive stop</code> || Остановить | ||
|Остановить | |||
|- | |- | ||
|<code>/etc/init.d/naive restart</code> | | <code>/etc/init.d/naive restart</code> || Перезапустить | ||
|Перезапустить | |||
|- | |- | ||
|<code>/etc/init.d/naive status</code> | | <code>/etc/init.d/naive status</code> || Статус | ||
|Статус | |||
|- | |- | ||
|<code>/etc/init.d/naive enable</code> | | <code>/etc/init.d/naive enable</code> || Добавить в автозапуск | ||
|Добавить в автозапуск | |||
|- | |- | ||
|<code>/etc/init.d/naive disable</code> | | <code>/etc/init.d/naive disable</code> || Убрать из автозапуска | ||
|Убрать из автозапуска | |||
|} | |} | ||
---- | ---- | ||
== Часть 4: Управление пользователями (утилита naive-users) == | == Часть 4: Управление пользователями (утилита naive-users) == | ||
=== 4.1 | === Что это и зачем === | ||
В рамках данного сетапа была написана специализированная CLI-утилита <code>naive-users.sh</code> — интерактивное меню для управления клиентами NaïveProxy непосредственно на Entry Node (RU сервере). | |||
Проблема, которую она решает: Caddy хранит учётные данные клиентов (логин + пароль) в виде plain-text строк <code>basic_auth</code> внутри <code>Caddyfile</code>. Добавление, удаление или просмотр пользователей вручную требует: | |||
# зайти на сервер, | |||
# открыть <code>/opt/caddy-naive/Caddyfile</code> в редакторе, | |||
# найти нужные строки, | |||
# не ошибиться в формате, | |||
# перезапустить Caddy через <code>docker compose restart</code>. | |||
Утилита автоматизирует весь этот процесс. Она работает непосредственно с <code>Caddyfile</code>, перезапускает Caddy после каждого изменения и сразу выводит готовый <code>config.json</code> для нового клиента. | |||
'''Ключевые возможности:''' | |||
{| class="wikitable" | |||
|- | |||
! Функция !! Описание | |||
|- | |||
| Список клиентов || Показывает всех существующих пользователей из Caddyfile | |||
|- | |||
| Добавить клиента || Запрашивает имя; пароль вводится вручную или генерируется автоматически (20 символов, <code>openssl rand</code>) | |||
|- | |||
| Удалить клиента || Выбор из списка + подтверждение, после — автоперезапуск Caddy | |||
|- | |||
| Показать конфиг || Читает логин/пароль из Caddyfile и выводит готовый <code>config.json</code> для ПК или роутера | |||
|} | |||
Caddy поддерживает произвольное количество <code>basic_auth</code> записей — каждый клиент имеет собственные логин и пароль, но все используют одну и ту же цепочку: | |||
<pre> | |||
Entry Node Caddy → naive daemon → Exit Node Caddy → WARP → Интернет | |||
</pre> | |||
; Важно: отзыв доступа происходит мгновенно — удалённый пользователь получит ошибку аутентификации при следующей попытке соединения, без перезапуска клиентского устройства. | |||
=== 4.1 Расположение файла === | |||
Скрипт хранится в репозитории по пути <code>scripts/naive-users.sh</code>. Файл изначально сохранён с Unix-окончаниями строк (LF) — дополнительная конвертация не требуется. | |||
=== 4.2 Установка на сервер === | |||
Скопируй файл с рабочей машины на Entry Node сервер: | |||
<syntaxhighlight lang="bash"> | |||
# На рабочей машине (Windows PowerShell): | # На рабочей машине (Windows PowerShell): | ||
scp naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh | scp scripts/naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh | ||
</syntaxhighlight>На | </syntaxhighlight> | ||
На сервере — сделать исполняемым и (опционально) создать короткий алиас: | |||
<syntaxhighlight lang="bash"> | |||
chmod +x /usr/local/bin/naive-users.sh | chmod +x /usr/local/bin/naive-users.sh | ||
# | # Опционально: алиас для быстрого запуска | ||
echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc | echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc | ||
source ~/.bashrc | source ~/.bashrc | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== 4. | ; Внимание: перед запуском отредактируй переменную <code>DOMAIN</code> внутри скрипта — укажи свой реальный домен Entry Node Caddy (строка <code>DOMAIN="..."</code> в начале файла). | ||
=== 4.3 Запуск === | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
sudo bash /usr/local/bin/naive-users.sh | sudo bash /usr/local/bin/naive-users.sh | ||
# или | # или, если создан алиас: | ||
naive-users | naive-users | ||
</syntaxhighlight>Главное меню:<pre> | </syntaxhighlight> | ||
Утилита требует прав root: она читает и изменяет <code>/opt/caddy-naive/Caddyfile</code> и перезапускает Docker-контейнер. | |||
Главное меню: | |||
<pre> | |||
╔══════════════════════════════════════╗ | ╔══════════════════════════════════════╗ | ||
║ NaïveProxy User Manager (RU) ║ | ║ NaïveProxy User Manager (RU) ║ | ||
| Строка 638: | Строка 874: | ||
</pre> | </pre> | ||
=== 4. | === 4.4 Примеры работы === | ||
==== Список клиентов ==== | ==== Список клиентов ==== | ||
Показывает нумерованный список всех <code>basic_auth</code> записей из Caddyfile.<pre> | |||
Показывает нумерованный список всех <code>basic_auth</code>-записей из Caddyfile. | |||
<pre> | |||
=== Список клиентов === | === Список клиентов === | ||
1. alice | 1. alice | ||
| Строка 651: | Строка 890: | ||
==== Добавить клиента ==== | ==== Добавить клиента ==== | ||
Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий). После добавления Caddy автоматически перезапускается и сразу выводится готовый <code>config.json</code> для клиента.<pre> | |||
Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий, на базе <code>openssl rand</code>). После добавления Caddy автоматически перезапускается и сразу выводится готовый <code>config.json</code> для клиента. | |||
<pre> | |||
=== Добавить клиента === | === Добавить клиента === | ||
Имя пользователя: alice | Имя пользователя: alice | ||
| Строка 676: | Строка 918: | ||
==== Удалить клиента ==== | ==== Удалить клиента ==== | ||
Показывает список, просит указать номер, требует подтверждение (y/N), затем удаляет строку из Caddyfile и перезапускает Caddy. | |||
Показывает список, просит указать номер, требует подтверждение <code>(y/N)</code>, затем удаляет строку <code>basic_auth</code> из Caddyfile и перезапускает Caddy. | |||
==== Показать конфиг клиента ==== | ==== Показать конфиг клиента ==== | ||
Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый <code>config.json</code>, который нужно скопировать на клиентское устройство. | Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый <code>config.json</code>, который нужно скопировать на клиентское устройство. | ||
=== 4. | === 4.5 Исходный код === | ||
RU | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%"> | ||
</ | <div style="font-weight:bold;line-height:1.6;padding:4px 0">▶ naive-users.sh — показать исходный код</div> | ||
<div class="mw-collapsible-content"> | |||
<syntaxhighlight lang="bash"> | |||
#!/bin/bash | |||
# ============================================================= | |||
# naive-users — управление пользователями NaïveProxy на RU сервере | |||
# Caddyfile: /opt/caddy-naive/Caddyfile | |||
# ============================================================= | |||
CADDYFILE="/opt/caddy-naive/Caddyfile" | |||
CADDY_DIR="/opt/caddy-naive" | |||
# Подставьте свой домен (не коммитьте реальные значения в публичный репозиторий) | |||
DOMAIN="your-naive-ru-domain.example.com" | |||
RED='\033[0;31m' | |||
GREEN='\033[0;32m' | |||
YELLOW='\033[1;33m' | |||
CYAN='\033[0;36m' | |||
BOLD='\033[1m' | |||
NC='\033[0m' | |||
# --- Вспомогательные функции --- | |||
check_root() { | |||
if [[ $EUID -ne 0 ]]; then | |||
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash naive-users.sh)${NC}" | |||
exit 1 | |||
fi | |||
} | |||
check_caddyfile() { | |||
if [[ ! -f "$CADDYFILE" ]]; then | |||
echo -e "${RED}Ошибка: Caddyfile не найден: $CADDYFILE${NC}" | |||
exit 1 | |||
fi | |||
} | |||
# Возвращает массив пользователей из Caddyfile | |||
get_users() { | |||
grep -oP '(?<=basic_auth )\S+' "$CADDYFILE" | |||
} | |||
# Возвращает пароль пользователя | |||
get_password() { | |||
local user="$1" | |||
grep "basic_auth $user " "$CADDYFILE" | awk '{print $3}' | |||
} | |||
restart_caddy() { | |||
echo -e "${YELLOW}Перезапуск Caddy...${NC}" | |||
cd "$CADDY_DIR" && docker compose restart caddy-naive > /dev/null 2>&1 | |||
if [[ $? -eq 0 ]]; then | |||
echo -e "${GREEN}Caddy перезапущен успешно.${NC}" | |||
else | |||
echo -e "${RED}Ошибка при перезапуске Caddy. Проверь: cd $CADDY_DIR && docker compose logs${NC}" | |||
fi | |||
} | |||
press_enter() { | |||
echo "" | |||
read -rp "Нажми Enter для возврата в меню..." | |||
} | |||
# --- Функции меню --- | |||
list_users() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Список клиентов ===${NC}" | |||
mapfile -t users < <(get_users) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Клиентов нет.${NC}" | |||
else | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo "" | |||
echo -e " Всего: ${#users[@]} клиент(ов)" | |||
fi | |||
press_enter | |||
} | |||
add_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Добавить клиента ===${NC}" | |||
read -rp "Имя пользователя: " username | |||
username=$(echo "$username" | tr -d ' ') | |||
if [[ -z "$username" ]]; then | |||
echo -e "${RED}Имя не может быть пустым.${NC}" | |||
press_enter | |||
return | |||
fi | |||
# Проверка — уже существует? | |||
if grep -q "basic_auth $username " "$CADDYFILE"; then | |||
echo -e "${RED}Пользователь '$username' уже существует.${NC}" | |||
press_enter | |||
return | |||
fi | |||
read -rp "Пароль (Enter = сгенерировать автоматически): " password | |||
if [[ -z "$password" ]]; then | |||
password=$(openssl rand -base64 16 | tr -d '=/+' | head -c 20) | |||
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}" | |||
fi | |||
# Вставить строку basic_auth перед первой строкой upstream или probe_resistance | |||
sed -i "/probe_resistance/i\\ basic_auth $username $password" "$CADDYFILE" | |||
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}" | |||
restart_caddy | |||
echo "" | |||
echo -e "${BOLD}Конфиг для клиента:${NC}" | |||
print_config_for "$username" "$password" | |||
press_enter | |||
} | |||
delete_user() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Удалить клиента ===${NC}" | |||
mapfile -t users < <(get_users) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет клиентов для удаления.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер для удаления: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
target="${users[$((choice-1))]}" | |||
read -rp "Удалить пользователя '${target}'? (y/N): " confirm | |||
if [[ "$confirm" =~ ^[Yy]$ ]]; then | |||
sed -i "/basic_auth $target /d" "$CADDYFILE" | |||
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}" | |||
restart_caddy | |||
else | |||
echo "Отменено." | |||
fi | |||
press_enter | |||
} | |||
print_config_for() { | |||
local user="$1" | |||
local pass="$2" | |||
echo "" | |||
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}" | |||
echo -e "${BOLD}│ config.json для naive (ПК / роутер) │${NC}" | |||
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}" | |||
echo '{ | |||
"listen": "socks://127.0.0.1:1080", | |||
"proxy": "https://'"$user"':'"$pass"'@'"$DOMAIN"'", | |||
"log": "" | |||
}' | |||
echo "" | |||
echo -e "${CYAN}Путь на роутере OpenWrt:${NC} /etc/naive/config.json" | |||
echo -e "${CYAN}Путь на Windows ПК:${NC} рядом с naive.exe" | |||
echo -e "${CYAN}SOCKS5 адрес для podkop:${NC} socks5://127.0.0.1:1080" | |||
} | |||
show_config() { | |||
echo "" | |||
echo -e "${BOLD}${CYAN}=== Показать конфиг клиента ===${NC}" | |||
mapfile -t users < <(get_users) | |||
if [[ ${#users[@]} -eq 0 ]]; then | |||
echo -e "${YELLOW}Нет клиентов.${NC}" | |||
press_enter | |||
return | |||
fi | |||
for i in "${!users[@]}"; do | |||
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}" | |||
done | |||
echo " 0. Отмена" | |||
echo "" | |||
read -rp "Выбери номер клиента: " choice | |||
if [[ "$choice" == "0" || -z "$choice" ]]; then | |||
return | |||
fi | |||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then | |||
echo -e "${RED}Неверный номер.${NC}" | |||
press_enter | |||
return | |||
fi | |||
local user="${users[$((choice-1))]}" | |||
local pass | |||
pass=$(get_password "$user") | |||
echo -e "Клиент: ${BOLD}${user}${NC}" | |||
print_config_for "$user" "$pass" | |||
press_enter | |||
} | |||
# --- Главное меню --- | |||
main_menu() { | |||
check_root | |||
check_caddyfile | |||
while true; do | |||
clear | |||
echo -e "${BOLD}${CYAN}" | |||
echo "╔══════════════════════════════════════╗" | |||
echo "║ NaïveProxy User Manager (RU) ║" | |||
echo "║ Домен: $DOMAIN ║" | |||
echo "╚══════════════════════════════════════╝" | |||
echo -e "${NC}" | |||
mapfile -t users < <(get_users) | |||
echo -e " Активных клиентов: ${BOLD}${#users[@]}${NC}" | |||
echo "" | |||
echo -e " ${BOLD}1.${NC} Список клиентов" | |||
echo -e " ${BOLD}2.${NC} Добавить клиента" | |||
echo -e " ${BOLD}3.${NC} Удалить клиента" | |||
echo -e " ${BOLD}4.${NC} Показать конфиг клиента" | |||
echo -e " ${BOLD}0.${NC} Выход" | |||
echo "" | |||
read -rp "Выбор: " option | |||
case "$option" in | |||
1) list_users ;; | |||
2) add_user ;; | |||
3) delete_user ;; | |||
4) show_config ;; | |||
0) echo "Выход."; exit 0 ;; | |||
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;; | |||
esac | |||
done | |||
} | |||
main_menu | |||
</syntaxhighlight> | |||
</div> | |||
</div> | |||
---- | ---- | ||
| Строка 694: | Строка 1190: | ||
=== 5.1 Пошаговая проверка === | === 5.1 Пошаговая проверка === | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
|1 | ! Шаг !! Команда !! Где выполнять !! Ожидаемый результат | ||
|<code>curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me</code> | |- | ||
|EU сервер | | 1 || <code>curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me</code> || EU сервер || Cloudflare WARP IP (104.x.x.x) | ||
|Cloudflare WARP IP (104.x.x.x) | |||
|- | |- | ||
|2 | | 2 || <code>curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me</code> || RU сервер || Тот же WARP IP | ||
|<code>curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me</code> | |||
|RU сервер | |||
|Тот же WARP IP | |||
|- | |- | ||
|3 | | 3 || <code>curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me</code> || OpenWrt роутер || Тот же WARP IP | ||
|<code>curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me</code> | |||
|OpenWrt роутер | |||
|Тот же WARP IP | |||
|- | |- | ||
|4 | | 4 || Открыть браузер через podkop || Устройство в сети || IP отличается от домашнего | ||
|Открыть браузер через podkop | |||
|Устройство в сети | |||
|IP отличается от домашнего | |||
|} | |} | ||
=== 5.2 Проверка probe_resistance === | === 5.2 Проверка probe_resistance === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси | # Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси | ||
| Строка 729: | Строка 1213: | ||
=== 5.3 Просмотр логов === | === 5.3 Просмотр логов === | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
# Caddy (EU или RU сервер) | # Caddy (EU или RU сервер) | ||
| Строка 741: | Строка 1226: | ||
=== 5.4 Перезапуск компонентов === | === 5.4 Перезапуск компонентов === | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
|Caddy EU | ! Компонент !! Команда | ||
|<code>cd /opt/caddy-naive && docker compose restart</code> | |- | ||
| Caddy EU || <code>cd /opt/caddy-naive && docker compose restart</code> | |||
|- | |- | ||
|Caddy RU | | Caddy RU || <code>cd /opt/caddy-naive && docker compose restart</code> | ||
|<code>cd /opt/caddy-naive && docker compose restart</code> | |||
|- | |- | ||
|naive daemon RU | | naive daemon RU || <code>systemctl restart naive</code> | ||
|<code>systemctl restart naive</code> | |||
|- | |- | ||
|naive daemon OpenWrt | | naive daemon OpenWrt || <code>/etc/init.d/naive restart</code> | ||
|<code>/etc/init.d/naive restart</code> | |||
|} | |} | ||
---- | ---- | ||
| Строка 762: | Строка 1245: | ||
=== EU сервер === | === EU сервер === | ||
<pre> | <pre> | ||
/opt/caddy-naive/ | /opt/caddy-naive/ | ||
| Строка 772: | Строка 1256: | ||
=== RU сервер === | === RU сервер === | ||
<pre> | <pre> | ||
/opt/caddy-naive/ | /opt/caddy-naive/ | ||
| Строка 787: | Строка 1272: | ||
=== OpenWrt роутер === | === OpenWrt роутер === | ||
<pre> | <pre> | ||
/usr/bin/naive ← бинарник naive (архитектура роутера) | /usr/bin/naive ← бинарник naive (архитектура роутера) | ||
| Строка 792: | Строка 1278: | ||
/etc/init.d/naive ← автозапуск через procd (START=95) | /etc/init.d/naive ← автозапуск через procd (START=95) | ||
</pre> | </pre> | ||
---- | ---- | ||
== Приложение: Частые ошибки == | == Приложение: Частые ошибки == | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
|<code>requires go >= 1.25.0</code> при сборке | ! Ошибка !! Причина !! Решение | ||
|Системный Go устарел | |- | ||
|Dockerfile уже использует <code>golang:1.25-alpine</code> — ошибка не возникнет | | <code>requires go >= 1.25.0</code> при сборке || Системный Go устарел || Dockerfile уже использует <code>golang:1.25-alpine</code> — ошибка не возникнет | ||
|- | |- | ||
|<code>wrong version number</code> в curl через Caddy | | <code>wrong version number</code> в curl через Caddy || RU Caddy использует <code>upstream https://</code> вместо SOCKS5 || Заменить на <code>upstream socks5://127.0.0.1:10808</code> | ||
|RU Caddy использует <code>upstream https://</code> вместо SOCKS5 | |||
|Заменить на <code>upstream socks5://127.0.0.1:10808</code> | |||
|- | |- | ||
|<code>Content-Length: 0</code> для HTTP GET через Caddy | | <code>Content-Length: 0</code> для HTTP GET через Caddy || Нормальное поведение forwardproxy для не-CONNECT запросов || Не является ошибкой | ||
|Нормальное поведение forwardproxy для не-CONNECT запросов | |||
|Не является ошибкой | |||
|- | |- | ||
|<code>tar: invalid tar magic</code> на OpenWrt | | <code>tar: invalid tar magic</code> на OpenWrt || BusyBox tar не поддерживает .xz || Использовать <code>xzcat file.tar.xz | tar -xf -</code> | ||
|BusyBox tar не поддерживает .xz | |||
|tar -xf - | |||
|- | |- | ||
|<code>$'\r': command not found</code> в bash-скрипте | | <code>$'\r': command not found</code> в bash-скрипте || Windows CRLF окончания строк || Выполнить <code>sed -i 's/\r//' /путь/к/скрипту.sh</code> | ||
|Windows CRLF окончания строк | |||
|Выполнить <code>sed -i 's/\r//' /путь/к/скрипту.sh</code> | |||
|- | |- | ||
|Caddy не получает сертификат | | Caddy не получает сертификат || Порт 80 занят другим сервисом || Проверить <code>ss -tlnp | grep :80</code>, освободить порт | ||
|Порт 80 занят другим сервисом | |||
|grep :80, освободить порт | |||
|- | |- | ||
|naive daemon не подключается к EU | | naive daemon не подключается к EU || Неверные креды или домен в config.json || Проверить <code>journalctl -u naive</code>, перепроверить Caddyfile EU | ||
|Неверные креды или домен в config.json | |||
|Проверить <code>journalctl -u naive</code>, перепроверить Caddyfile EU | |||
|- | |- | ||
|probe_resistance возвращает ошибку прокси | | probe_resistance возвращает ошибку прокси || Отсутствует <code>file_server</code> блок в Caddyfile || Добавить <code>file_server { root /var/www/html }</code> | ||
|Отсутствует <code>file_server</code> блок в Caddyfile | |||
|Добавить <code>file_server { root /var/www/html }</code> | |||
|} | |} | ||
Текущая версия от 08:05, 26 апреля 2026
NaïveProxy: установка полной каскадной цепочки
Что такое NaïveProxy и зачем он нужен
Проблема: как работают системы блокировок
Современные системы глубокой инспекции пакетов (DPI — Deep Packet Inspection), которые применяются интернет-провайдерами и регуляторами по всему миру, умеют анализировать не только адреса, но и характер трафика. Даже если трафик зашифрован, по поведению соединения можно определить, что это не обычный браузер, а программа для обхода ограничений (VPN, Shadowsocks, обычный SOCKS5-прокси и т.д.).
Принцип прост: DPI «смотрит» на то, как именно клиент общается с сервером:
- сколько байт летит в каждую сторону,
- с какими временны́ми интервалами,
- какой у соединения «fingerprint» (цифровой отпечаток),
- как выглядит TLS-рукопожатие.
Обычный HTTPS-прокси легко выявляется, потому что программа-клиент общается иначе, чем реальный браузер.
Решение: маскировка под браузер Chrome
NaïveProxy решает эту проблему принципиально: клиентская часть (naive binary) притворяется настоящим браузером Chrome, который делает обычные HTTPS-запросы. Серверная часть (Caddy с плагином forwardproxy) притворяется обычным веб-сервером.
Ключевая особенность — NaïveProxy использует встроенный сетевой стек Chromium. Это значит, что трафик неотличим от реального Chrome не только по содержимому, но и по поведению на уровне TCP/TLS: те же задержки, те же размеры пакетов, тот же TLS fingerprint.
| Протокол | Как выглядит для DPI | Устойчивость к блокировке |
|---|---|---|
| Обычный VPN (WireGuard, OpenVPN) | Характерный шаблон UDP/TCP | Низкая — легко заблокировать по fingerprint |
| Shadowsocks | Случайный зашифрованный поток | Средняя — выявляется по энтропии и поведению |
| VLESS / VMess (без маскировки) | TLS, но нестандартный fingerprint | Средняя — детектируется active probing |
| VLESS + REALITY | TLS с реальным сертификатом стороннего сайта | Высокая — но заметен при активном зондировании |
| NaïveProxy | Неотличим от Chrome HTTPS | Очень высокая — устойчив к DPI и активному зондированию |
Как работает NaïveProxy технически
NaïveProxy использует протокол HTTP/2 CONNECT для туннелирования трафика. Упрощённо:
- Клиент (naive binary на вашем устройстве) открывает HTTPS-соединение с сервером — точь-в-точь как Chrome.
- Внутри этого HTTPS-соединения по протоколу HTTP/2 CONNECT прокидывается туннель для вашего трафика.
- Сервер (Caddy + forwardproxy) обрабатывает запросы как обычный веб-сервер, а туннельные запросы перенаправляет дальше.
- Если кто-то «постучится» на сервер без правильных credentials — сервер ответит как обычный сайт (probe_resistance — защита от зондирования).
Каскадная цепочка: зачем два сервера
В данном решении используется каскад из двух серверов: Entry Node (точка входа, ближайший к клиентам) и Exit Node (точка выхода в интернет). Это сделано намеренно:
- Снижение риска для клиента: клиенты подключаются только к ближайшему серверу-входу — подключение к локальному узлу менее заметно, чем прямое соединение с зарубежным сервером.
- Гибкость маршрутизации: Entry Node может обслуживать разных клиентов по разным протоколам (VLESS, Trojan, NaïveProxy) и перенаправлять их на Exit Node.
- Дополнительный слой анонимности: Exit Node выходит в интернет через Cloudflare WARP — конечный сайт видит IP Cloudflare, а не IP вашего сервера.
Схема конкретного решения
Компоненты инфраструктуры
| Компонент | Роль | IP / Адрес | ОС |
|---|---|---|---|
| Устройства домашней сети | Конечные пользователи | — | Любые |
| Роутер Xiaomi Redmi AX6000 | Прозрачный прокси для всей сети | — | OpenWrt 24.10 (aarch64) |
| RU сервер (Entry Node) | Точка входа (Entry Node) | YOUR_RU_SERVER_IP / your-naive-ru-domain.example.com |
Ubuntu 24.04 |
| EU сервер (Exit Node) | Точка выхода (Exit Node) | YOUR_EU_SERVER_IP / your-naive-eu-domain.example.com |
Ubuntu 24.04 |
| Cloudflare WARP | Финальный выход в интернет | IP Cloudflare | — |
Полная схема прохождения трафика
Протоколы на каждом участке цепочки
| Участок | Протокол | Шифрование | Что видит наблюдатель |
|---|---|---|---|
| Устройство → Роутер | Обычный TCP/UDP | Нет (локальная сеть) | Локальный трафик |
| Роутер → RU Caddy | HTTPS HTTP/2 (NaïveProxy) | TLS 1.3 (Let's Encrypt) | Браузер Chrome обращается к сайту |
| RU naive → EU Caddy | HTTPS HTTP/2 (NaïveProxy) | TLS 1.3 (Let's Encrypt) | Браузер Chrome обращается к сайту |
| EU Caddy → 3x-ui inbound | SOCKS5 (localhost) | — (локально) | Только на EU сервере |
| EU 3x-ui → WARP | WireGuard (gVisor) | WireGuard | Зашифрованный WireGuard к Cloudflare |
| WARP → Интернет | Обычный TCP/UDP | — | IP Cloudflare |
Дополнительные каналы (параллельно NaïveProxy)
| Канал | Протокол | Маршрут | Статус |
|---|---|---|---|
| 3x-ui каскад (основной) | VLESS / REALITY | Клиенты → RU 3x-ui → EU 3x-ui → Интернет | ✅ Активен |
| NaïveProxy каскад (этот гайд) | HTTP/2 naïve | Роутер/ПК → RU Caddy → EU Caddy → WARP | ✅ Активен |
| MTProto (Telegram) | MTProto | Клиенты Telegram → EU :8443, :2443 | ✅ Активен |
| Hysteria2 | QUIC/UDP | → EU | ⏸ Конфиг есть, сервис не запущен |
Данный гайд описывает пошаговое развёртывание каскадного прокси-сервера на базе протокола NaïveProxy. Трафик проходит полную цепочку:
Устройства → OpenWrt роутер (naive) → RU сервер (Caddy + naive) → EU сервер (Caddy) → WARP → Интернет
На серверах предполагается, что 3x-ui уже установлен и работает.
Предварительные требования
Инфраструктура
| Компонент | Требования |
|---|---|
| EU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| RU сервер | Ubuntu 24.04, публичный IP, домен с A-записью → IP сервера |
| OpenWrt роутер | OpenWrt 22.03+, архитектура aarch64_cortex-a53 или другая |
| Порт 80 и 443 | Должны быть свободны на обоих серверах (3x-ui не занимает эти порты) |
DNS-записи (создать до начала)
| Запись | Тип | Значение |
|---|---|---|
your-naive-ru-domain.example.com
|
A | IP RU сервера |
your-naive-eu-domain.example.com
|
A | IP EU сервера |
Используемые заглушки
В данном руководстве применяются следующие заглушки. Замени их реальными значениями:
| Заглушка | Описание |
|---|---|
YOUR_RU_SERVER_IP
|
Публичный IP RU сервера |
YOUR_EU_SERVER_IP
|
Публичный IP EU сервера |
your-naive-ru-domain.example.com
|
Домен для RU сервера (Caddy) |
your-naive-eu-domain.example.com
|
Домен для EU сервера (Caddy) |
YOUR_EMAIL@example.com
|
Email для Let's Encrypt (уведомления об истечении сертификата) |
RU_PROXY_USER
|
Логин пользователя для RU Caddy |
RU_PROXY_PASSWORD
|
Пароль пользователя для RU Caddy |
EU_PROXY_USER
|
Логин пользователя для EU Caddy |
EU_PROXY_PASSWORD
|
Пароль пользователя для EU Caddy |
Часть 1: Настройка EU сервера (Exit Node)
Цель: развернуть Caddy с плагином forwardproxy (NaïveProxy) в Docker. Caddy будет принимать зашифрованные соединения от RU сервера и выпускать трафик через WARP.
1.1 Установка Docker
Выполни на EU сервере:
# Обновить пакеты
apt-get update && apt-get upgrade -y
# Установить зависимости
apt-get install -y ca-certificates curl gnupg lsb-release
# Добавить GPG-ключ Docker
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Добавить репозиторий
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
# Установить Docker
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Проверить
docker --version
docker compose version
Ожидаемый вывод:
Docker version 27.x.x, build ... Docker Compose version v2.x.x
1.2 Создание структуры каталогов
mkdir -p /opt/caddy-naive/www
cd /opt/caddy-naive
1.3 Создание Dockerfile
Caddy собирается с нестандартным плагином forwardproxy (форк klzgrad/forwardproxy@naive).
Используется Go 1.25+ — обязательное требование для текущей версии Caddy.
cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
RUN xcaddy build \
--with github.com/caddyserver/forwardproxy=github.com/klzgrad/forwardproxy@naive
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /go/caddy /usr/local/bin/caddy
EXPOSE 80 443
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
EOF
- Почему Go 1.25-alpine?
- Текущая версия Caddy требует Go >= 1.25. Системный Go в Ubuntu 24.04 — версия 1.22, которая не подходит. Поэтому сборка происходит внутри Docker-контейнера с нужной версией.
1.4 Создание docker-compose.yml
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
services:
caddy-naive:
build: .
container_name: caddy-naive
restart: unless-stopped
network_mode: host
environment:
- XDG_DATA_HOME=/data
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./www:/var/www/html:ro
- caddy_data:/data
- caddy_logs:/var/log/caddy
volumes:
caddy_data:
caddy_logs:
EOF
- Почему XDG_DATA_HOME=/data?
- Кастомный образ Caddy (собранный из
debian:bookworm-slim) не наследует переменные официального Docker образа Caddy. Без этой переменной Caddy хранит сертификаты и служебные данные в/root/.local/share/caddy/— внутри writable layer контейнера. При пересборке или пересоздании контейнера все сертификаты теряются, и Caddy запрашивает новые у Let's Encrypt (лимит: 5 сертификатов на домен в неделю). СXDG_DATA_HOME=/dataданные пишутся в/data/caddy/, который примонтирован как named volume — и переживают любые пересборки.
- Почему network_mode
- host?
- Контейнер использует сетевой стек хоста напрямую. Это необходимо чтобы:
- Caddy получал реальные клиентские IP для корректной работы probe_resistance
- Контейнер мог обращаться к
127.0.0.1:24363(Xray inbound WARP) — локальному сервису хоста
1.5 Создание заглушки для probe_resistance
cat > /opt/caddy-naive/www/index.html << 'EOF'
<!DOCTYPE html>
<html><head><title>Welcome</title></head>
<body><h1>Welcome</h1><p>Nothing to see here.</p></body>
</html>
EOF
- Зачем это нужно?
- Функция
probe_resistanceв Caddy скрывает факт наличия прокси. - При обращении без авторизации сервер возвращает эту обычную HTML-страницу вместо ответа прокси.
- Это защищает от автоматических сканеров и активных DPI-проверок.
1.6 Создание Caddyfile
cat > /opt/caddy-naive/Caddyfile << 'EOF'
{
order forward_proxy before file_server
}
:443, your-naive-eu-domain.example.com {
tls YOUR_EMAIL@example.com
forward_proxy {
basic_auth EU_PROXY_USER EU_PROXY_PASSWORD
upstream socks5://127.0.0.1:24363
hide_ip
hide_via
probe_resistance
}
file_server {
root /var/www/html
}
}
EOF
- Важные нюансы синтаксиса
- *
:443, your-naive-eu-domain.example.com— запятая и пробел обязательны. Именно такой синтаксис активирует HTTP/2 для CONNECT-туннелей, без которого NaïveProxy не работает - *
upstream socks5://127.0.0.1:24363— перенаправляет исходящий трафик в Xray inbound (WARP). Если WARP ещё не настроен, эту строку можно убрать — трафик пойдёт напрямую в интернет - *
probe_resistance— без параметра означает ответ HTML-заглушкой на неавторизованные запросы
1.7 Настройка Xray inbound для WARP (в 3x-ui)
Данный шаг необходим чтобы выходящий трафик от Caddy уходил через Cloudflare WARP, а не напрямую в интернет.
В панели 3x-ui на EU сервере:
- Перейди в раздел "Подключения" → "Создать подключение"
- Заполни параметры:
- Протокол:
mixed - Мониторинг IP:
127.0.0.1 - IP:
127.0.0.1 - Порт:
24363(или любой свободный) - Пароль: выключен
- UDP: включён
- Примечание:
naive-Caddy
- Протокол:
- Сохрани подключение
Создать routing rule (Xray настройки → Маршрутизация):
- Добавь правило: inbound tag
naive-Caddy→ outbound WARP - Это направит весь трафик от Caddy через Cloudflare WARP
- Проверка WARP inbound
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
# Должен вернуть IP из диапазона Cloudflare: 104.x.x.x или 162.159.x.x
1.8 Сборка и запуск
cd /opt/caddy-naive
# Сборка образа (занимает 3-5 минут — компилируется Go)
docker compose build
# Запуск в фоне
docker compose up -d
# Наблюдение за логами (получение сертификата занимает ~30 секунд)
docker compose logs -f
Ожидаемый вывод в логах:
...
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-eu-domain.example.com"}
...
1.9 Проверка EU сервера
# Проверка probe_resistance — должна вернуть HTML страницу (не ошибку прокси)
curl -sv https://your-naive-eu-domain.example.com 2>&1 | grep -E "< HTTP|title"
# Проверка прокси-функциональности через SOCKS5 inbound
curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me
Часть 2: Настройка RU сервера (Entry Node)
Цель: на RU сервере поднять два компонента:
- Caddy (Docker) — принимает соединения от клиентов (роутеров), пробрасывает через локальный naive daemon
- naive daemon (systemd) — клиент, который соединяется с EU Caddy по протоколу NaïveProxy
2.1 Установка Docker
Те же команды, что и для EU сервера:
apt-get update && apt-get upgrade -y
apt-get install -y ca-certificates curl gnupg lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker --version && docker compose version
2.2 Структура каталогов и файлы Docker
mkdir -p /opt/caddy-naive/www
cd /opt/caddy-naive
Dockerfile идентичен EU серверу:
cat > /opt/caddy-naive/Dockerfile << 'EOF'
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
RUN xcaddy build \
--with github.com/caddyserver/forwardproxy=github.com/klzgrad/forwardproxy@naive
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /go/caddy /usr/local/bin/caddy
EXPOSE 80 443
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
EOF
cat > /opt/caddy-naive/docker-compose.yml << 'EOF'
services:
caddy-naive:
build: .
container_name: caddy-naive
restart: unless-stopped
network_mode: host
environment:
- XDG_DATA_HOME=/data
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./www:/var/www/html:ro
- caddy_data:/data
- caddy_logs:/var/log/caddy
volumes:
caddy_data:
caddy_logs:
EOF
cat > /opt/caddy-naive/www/index.html << 'EOF'
<!DOCTYPE html>
<html><head><title>Welcome</title></head>
<body><h1>Welcome</h1><p>Nothing to see here.</p></body>
</html>
EOF
2.3 Создание Caddyfile RU сервера
Ключевое отличие от EU: директива upstream socks5://127.0.0.1:10808, которая перенаправляет входящие CONNECT-запросы в локальный naive daemon.
cat > /opt/caddy-naive/Caddyfile << 'EOF'
{
order forward_proxy before file_server
}
:443, your-naive-ru-domain.example.com {
tls YOUR_EMAIL@example.com
forward_proxy {
basic_auth RU_PROXY_USER RU_PROXY_PASSWORD
upstream socks5://127.0.0.1:10808
hide_ip
hide_via
probe_resistance
}
file_server {
root /var/www/html
}
}
EOF
- Почему upstream socks5, а не https?
- Вариант
upstream https://EU_CADDYне работает в данной схеме — Caddy при таком подходе "протекает" заголовки HTTP-ответа в TLS-туннель, что ломает рукопожатие. Правильное решение — цепочка через SOCKS5 к бинарнику naive, который уже корректно реализует протокол NaïveProxy.
2.4 Сборка и запуск Caddy
cd /opt/caddy-naive
docker compose build
docker compose up -d
docker compose logs -f
Ожидаемый вывод:
{"level":"info","msg":"certificate obtained successfully","identifier":"your-naive-ru-domain.example.com"}
2.5 Установка naive daemon (клиент для EU)
2.5.1 Определение архитектуры сервера
uname -m
# x86_64 → нужен пакет для linux-x86_64-static
2.5.2 Скачивание бинарника naive
Перейди на страницу релизов: https://github.com/klzgrad/naiveproxy/releases
Найди архив для своей архитектуры. Для Ubuntu x86_64:
naiveproxy-vXXX-linux-x86_64.tar.xz
# Скачать (замени URL на актуальный из Releases)
cd /tmp
wget https://github.com/klzgrad/naiveproxy/releases/download/vXXX/naiveproxy-vXXX-linux-x86_64.tar.xz
# Распаковать (xzcat нужен если tar не поддерживает .xz напрямую)
tar -xf naiveproxy-vXXX-linux-x86_64.tar.xz
# или
xzcat naiveproxy-vXXX-linux-x86_64.tar.xz | tar -xf -
# Установить бинарник
cp naiveproxy-vXXX-linux-x86_64/naive /usr/bin/naive
chmod +x /usr/bin/naive
# Проверить
naive --version
2.5.3 Создание конфигурации naive daemon
mkdir -p /etc/naive
cat > /etc/naive/config.json << 'EOF'
{
"listen": "socks://127.0.0.1:10808",
"proxy": "https://EU_PROXY_USER:EU_PROXY_PASSWORD@your-naive-eu-domain.example.com",
"log": ""
}
EOF
- Что происходит
-
listen— naive слушает SOCKS5 на localhost:10808 (именно сюда смотрит upstream в Caddyfile RU)proxy— подключается к EU Caddy по протоколу NaïveProxy (HTTP/2 CONNECT over TLS, трафик неотличим от обычного Chrome HTTPS)
2.5.4 Создание systemd-сервиса
cat > /etc/systemd/system/naive.service << 'EOF'
[Unit]
Description=NaïveProxy client daemon
After=network.target
[Service]
ExecStart=/usr/bin/naive /etc/naive/config.json
Restart=always
RestartSec=5
User=nobody
[Install]
WantedBy=multi-user.target
EOF
# Активировать и запустить
systemctl daemon-reload
systemctl enable naive
systemctl start naive
# Проверить статус
systemctl status naive
Ожидаемый вывод:
● naive.service - NaïveProxy client daemon
Loaded: loaded (/etc/systemd/system/naive.service; enabled)
Active: active (running)
2.6 Проверка цепочки с RU сервера
# Тест HTTP через naive daemon → EU → WARP
curl -s -x socks5h://127.0.0.1:10808 http://ifconfig.me
# Должен вернуть IP EU сервера или Cloudflare WARP IP
# Тест HTTPS
curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me
# Должен вернуть тот же результат
Часть 3: Настройка OpenWrt роутера
Цель: запустить naive как клиент на роутере, подключить podkop для избирательной маршрутизации через RU Caddy.
3.1 Определение архитектуры роутера
uname -m
# aarch64 → нужен пакет openwrt-aarch64_cortex-a53-static
# или x86_64, mipsel, armv7l — зависит от модели роутера
# Подробнее об архитектуре
cat /etc/openwrt_release | grep ARCH
3.2 Скачивание бинарника naive для OpenWrt
На странице релизов https://github.com/klzgrad/naiveproxy/releases найди архив:
naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz
(замени архитектуру на свою)
# Создать каталог
mkdir -p /etc/naive
# Скачать (замени URL)
cd /tmp
wget https://github.com/klzgrad/naiveproxy/releases/download/vXXX/naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz
- Важно
- OpenWrt использует BusyBox tar, который не поддерживает .xz напрямую.
Используй pipe с xzcat:
# Если xz не установлен:
opkg update && opkg install xz
# Распаковать через pipe
xzcat naiveproxy-vXXX-openwrt-aarch64_cortex-a53.tar.xz | tar -xf -
# Установить бинарник
cp naiveproxy-vXXX-openwrt-aarch64_cortex-a53/naive /usr/bin/naive
chmod +x /usr/bin/naive
# Проверить
naive --version
3.3 Создание конфигурации
cat > /etc/naive/config.json << 'EOF'
{
"listen": "socks://127.0.0.1:1080",
"proxy": "https://RU_PROXY_USER:RU_PROXY_PASSWORD@your-naive-ru-domain.example.com",
"log": ""
}
EOF
- Чем этот конфиг отличается от конфига на RU сервере
-
- Роутер подключается к RU Caddy (не к EU напрямую) — это первое звено каскада
- Слушает на порту 1080 (стандарт для SOCKS5, podkop по умолчанию ожидает его)
3.4 Создание init-скрипта procd
cat > /etc/init.d/naive << 'EOF'
#!/bin/sh /etc/rc.common
START=95
STOP=01
USE_PROCD=1
start_service() {
procd_open_instance
procd_set_param command /usr/bin/naive /etc/naive/config.json
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
EOF
chmod +x /etc/init.d/naive
- Почему START=95?
- Сервис должен запускаться после сети (START=20) и других зависимостей. 95 — безопасное значение.
3.5 Запуск и автозапуск
# Включить автозапуск
/etc/init.d/naive enable
# Запустить сейчас
/etc/init.d/naive start
# Проверить статус
/etc/init.d/naive status
3.6 Проверка
# Тест подключения через наивный прокси
curl -s -x socks5h://127.0.0.1:1080 http://ifconfig.me
# Должен вернуть IP WARP или EU сервера (НЕ IP роутера или RU сервера)
3.7 Настройка podkop
В интерфейсе podkop (LuCI или CLI):
- Тип прокси: SOCKS5
- Адрес:
socks5://127.0.0.1:1080 - Сохрани и применй настройки
После этого все устройства домашней сети, трафик которых podkop перенаправляет, будут автоматически идти через полную цепочку: Роутер → RU Caddy → EU Caddy → WARP → Интернет.
3.8 Управление сервисом на роутере
| Команда | Действие |
|---|---|
/etc/init.d/naive start |
Запустить |
/etc/init.d/naive stop |
Остановить |
/etc/init.d/naive restart |
Перезапустить |
/etc/init.d/naive status |
Статус |
/etc/init.d/naive enable |
Добавить в автозапуск |
/etc/init.d/naive disable |
Убрать из автозапуска |
Часть 4: Управление пользователями (утилита naive-users)
Что это и зачем
В рамках данного сетапа была написана специализированная CLI-утилита naive-users.sh — интерактивное меню для управления клиентами NaïveProxy непосредственно на Entry Node (RU сервере).
Проблема, которую она решает: Caddy хранит учётные данные клиентов (логин + пароль) в виде plain-text строк basic_auth внутри Caddyfile. Добавление, удаление или просмотр пользователей вручную требует:
- зайти на сервер,
- открыть
/opt/caddy-naive/Caddyfileв редакторе, - найти нужные строки,
- не ошибиться в формате,
- перезапустить Caddy через
docker compose restart.
Утилита автоматизирует весь этот процесс. Она работает непосредственно с Caddyfile, перезапускает Caddy после каждого изменения и сразу выводит готовый config.json для нового клиента.
Ключевые возможности:
| Функция | Описание |
|---|---|
| Список клиентов | Показывает всех существующих пользователей из Caddyfile |
| Добавить клиента | Запрашивает имя; пароль вводится вручную или генерируется автоматически (20 символов, openssl rand)
|
| Удалить клиента | Выбор из списка + подтверждение, после — автоперезапуск Caddy |
| Показать конфиг | Читает логин/пароль из Caddyfile и выводит готовый config.json для ПК или роутера
|
Caddy поддерживает произвольное количество basic_auth записей — каждый клиент имеет собственные логин и пароль, но все используют одну и ту же цепочку:
Entry Node Caddy → naive daemon → Exit Node Caddy → WARP → Интернет
- Важно
- отзыв доступа происходит мгновенно — удалённый пользователь получит ошибку аутентификации при следующей попытке соединения, без перезапуска клиентского устройства.
4.1 Расположение файла
Скрипт хранится в репозитории по пути scripts/naive-users.sh. Файл изначально сохранён с Unix-окончаниями строк (LF) — дополнительная конвертация не требуется.
4.2 Установка на сервер
Скопируй файл с рабочей машины на Entry Node сервер:
# На рабочей машине (Windows PowerShell):
scp scripts/naive-users.sh root@YOUR_RU_SERVER_IP:/usr/local/bin/naive-users.sh
На сервере — сделать исполняемым и (опционально) создать короткий алиас:
chmod +x /usr/local/bin/naive-users.sh
# Опционально: алиас для быстрого запуска
echo 'alias naive-users="sudo bash /usr/local/bin/naive-users.sh"' >> ~/.bashrc
source ~/.bashrc
- Внимание
- перед запуском отредактируй переменную
DOMAINвнутри скрипта — укажи свой реальный домен Entry Node Caddy (строкаDOMAIN="..."в начале файла).
4.3 Запуск
sudo bash /usr/local/bin/naive-users.sh
# или, если создан алиас:
naive-users
Утилита требует прав root: она читает и изменяет /opt/caddy-naive/Caddyfile и перезапускает Docker-контейнер.
Главное меню:
╔══════════════════════════════════════╗ ║ NaïveProxy User Manager (RU) ║ ║ Домен: your-naive-ru-domain... ║ ╚══════════════════════════════════════╝ Активных клиентов: 2 1. Список клиентов 2. Добавить клиента 3. Удалить клиента 4. Показать конфиг клиента 0. Выход
4.4 Примеры работы
Список клиентов
Показывает нумерованный список всех basic_auth-записей из Caddyfile.
=== Список клиентов === 1. alice 2. bob 3. router-home Всего: 3 клиент(ов)
Добавить клиента
Запрашивает имя. Пароль можно ввести вручную или нажать Enter — пароль сгенерируется автоматически (20 символов, криптостойкий, на базе openssl rand). После добавления Caddy автоматически перезапускается и сразу выводится готовый config.json для клиента.
=== Добавить клиента ===
Имя пользователя: alice
Пароль (Enter = сгенерировать автоматически): [Enter]
Сгенерирован пароль: xK9mPqR2nLvT8hQw3Yz5
Пользователь 'alice' добавлен.
Перезапуск Caddy...
Caddy перезапущен успешно.
┌─────────────────────────────────────────────┐
│ config.json для naive (ПК / роутер) │
└─────────────────────────────────────────────┘
{
"listen": "socks://127.0.0.1:1080",
"proxy": "https://alice:xK9mPqR2nLvT8hQw3Yz5@your-naive-ru-domain.example.com",
"log": ""
}
Путь на роутере OpenWrt: /etc/naive/config.json
Путь на Windows ПК: рядом с naive.exe
SOCKS5 адрес для podkop: socks5://127.0.0.1:1080
Удалить клиента
Показывает список, просит указать номер, требует подтверждение (y/N), затем удаляет строку basic_auth из Caddyfile и перезапускает Caddy.
Показать конфиг клиента
Выбрать существующего пользователя из списка — утилита прочитает логин и пароль из Caddyfile и выведет готовый config.json, который нужно скопировать на клиентское устройство.
4.5 Исходный код
#!/bin/bash
# =============================================================
# naive-users — управление пользователями NaïveProxy на RU сервере
# Caddyfile: /opt/caddy-naive/Caddyfile
# =============================================================
CADDYFILE="/opt/caddy-naive/Caddyfile"
CADDY_DIR="/opt/caddy-naive"
# Подставьте свой домен (не коммитьте реальные значения в публичный репозиторий)
DOMAIN="your-naive-ru-domain.example.com"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# --- Вспомогательные функции ---
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Ошибка: запусти скрипт от root (sudo bash naive-users.sh)${NC}"
exit 1
fi
}
check_caddyfile() {
if [[ ! -f "$CADDYFILE" ]]; then
echo -e "${RED}Ошибка: Caddyfile не найден: $CADDYFILE${NC}"
exit 1
fi
}
# Возвращает массив пользователей из Caddyfile
get_users() {
grep -oP '(?<=basic_auth )\S+' "$CADDYFILE"
}
# Возвращает пароль пользователя
get_password() {
local user="$1"
grep "basic_auth $user " "$CADDYFILE" | awk '{print $3}'
}
restart_caddy() {
echo -e "${YELLOW}Перезапуск Caddy...${NC}"
cd "$CADDY_DIR" && docker compose restart caddy-naive > /dev/null 2>&1
if [[ $? -eq 0 ]]; then
echo -e "${GREEN}Caddy перезапущен успешно.${NC}"
else
echo -e "${RED}Ошибка при перезапуске Caddy. Проверь: cd $CADDY_DIR && docker compose logs${NC}"
fi
}
press_enter() {
echo ""
read -rp "Нажми Enter для возврата в меню..."
}
# --- Функции меню ---
list_users() {
echo ""
echo -e "${BOLD}${CYAN}=== Список клиентов ===${NC}"
mapfile -t users < <(get_users)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Клиентов нет.${NC}"
else
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo ""
echo -e " Всего: ${#users[@]} клиент(ов)"
fi
press_enter
}
add_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Добавить клиента ===${NC}"
read -rp "Имя пользователя: " username
username=$(echo "$username" | tr -d ' ')
if [[ -z "$username" ]]; then
echo -e "${RED}Имя не может быть пустым.${NC}"
press_enter
return
fi
# Проверка — уже существует?
if grep -q "basic_auth $username " "$CADDYFILE"; then
echo -e "${RED}Пользователь '$username' уже существует.${NC}"
press_enter
return
fi
read -rp "Пароль (Enter = сгенерировать автоматически): " password
if [[ -z "$password" ]]; then
password=$(openssl rand -base64 16 | tr -d '=/+' | head -c 20)
echo -e " Сгенерирован пароль: ${BOLD}${password}${NC}"
fi
# Вставить строку basic_auth перед первой строкой upstream или probe_resistance
sed -i "/probe_resistance/i\\ basic_auth $username $password" "$CADDYFILE"
echo -e "${GREEN}Пользователь '${username}' добавлен.${NC}"
restart_caddy
echo ""
echo -e "${BOLD}Конфиг для клиента:${NC}"
print_config_for "$username" "$password"
press_enter
}
delete_user() {
echo ""
echo -e "${BOLD}${CYAN}=== Удалить клиента ===${NC}"
mapfile -t users < <(get_users)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет клиентов для удаления.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер для удаления: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
target="${users[$((choice-1))]}"
read -rp "Удалить пользователя '${target}'? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
sed -i "/basic_auth $target /d" "$CADDYFILE"
echo -e "${GREEN}Пользователь '${target}' удалён.${NC}"
restart_caddy
else
echo "Отменено."
fi
press_enter
}
print_config_for() {
local user="$1"
local pass="$2"
echo ""
echo -e "${BOLD}┌─────────────────────────────────────────────┐${NC}"
echo -e "${BOLD}│ config.json для naive (ПК / роутер) │${NC}"
echo -e "${BOLD}└─────────────────────────────────────────────┘${NC}"
echo '{
"listen": "socks://127.0.0.1:1080",
"proxy": "https://'"$user"':'"$pass"'@'"$DOMAIN"'",
"log": ""
}'
echo ""
echo -e "${CYAN}Путь на роутере OpenWrt:${NC} /etc/naive/config.json"
echo -e "${CYAN}Путь на Windows ПК:${NC} рядом с naive.exe"
echo -e "${CYAN}SOCKS5 адрес для podkop:${NC} socks5://127.0.0.1:1080"
}
show_config() {
echo ""
echo -e "${BOLD}${CYAN}=== Показать конфиг клиента ===${NC}"
mapfile -t users < <(get_users)
if [[ ${#users[@]} -eq 0 ]]; then
echo -e "${YELLOW}Нет клиентов.${NC}"
press_enter
return
fi
for i in "${!users[@]}"; do
echo -e " ${BOLD}$((i+1)).${NC} ${users[$i]}"
done
echo " 0. Отмена"
echo ""
read -rp "Выбери номер клиента: " choice
if [[ "$choice" == "0" || -z "$choice" ]]; then
return
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#users[@]} )); then
echo -e "${RED}Неверный номер.${NC}"
press_enter
return
fi
local user="${users[$((choice-1))]}"
local pass
pass=$(get_password "$user")
echo -e "Клиент: ${BOLD}${user}${NC}"
print_config_for "$user" "$pass"
press_enter
}
# --- Главное меню ---
main_menu() {
check_root
check_caddyfile
while true; do
clear
echo -e "${BOLD}${CYAN}"
echo "╔══════════════════════════════════════╗"
echo "║ NaïveProxy User Manager (RU) ║"
echo "║ Домен: $DOMAIN ║"
echo "╚══════════════════════════════════════╝"
echo -e "${NC}"
mapfile -t users < <(get_users)
echo -e " Активных клиентов: ${BOLD}${#users[@]}${NC}"
echo ""
echo -e " ${BOLD}1.${NC} Список клиентов"
echo -e " ${BOLD}2.${NC} Добавить клиента"
echo -e " ${BOLD}3.${NC} Удалить клиента"
echo -e " ${BOLD}4.${NC} Показать конфиг клиента"
echo -e " ${BOLD}0.${NC} Выход"
echo ""
read -rp "Выбор: " option
case "$option" in
1) list_users ;;
2) add_user ;;
3) delete_user ;;
4) show_config ;;
0) echo "Выход."; exit 0 ;;
*) echo -e "${RED}Неверный выбор.${NC}"; sleep 1 ;;
esac
done
}
main_menu
Часть 5: Проверка полной цепочки
5.1 Пошаговая проверка
| Шаг | Команда | Где выполнять | Ожидаемый результат |
|---|---|---|---|
| 1 | curl -s -x socks5h://127.0.0.1:24363 https://ifconfig.me |
EU сервер | Cloudflare WARP IP (104.x.x.x) |
| 2 | curl -s -x socks5h://127.0.0.1:10808 https://ifconfig.me |
RU сервер | Тот же WARP IP |
| 3 | curl -s -x socks5h://127.0.0.1:1080 https://ifconfig.me |
OpenWrt роутер | Тот же WARP IP |
| 4 | Открыть браузер через podkop | Устройство в сети | IP отличается от домашнего |
5.2 Проверка probe_resistance
# Запрос без авторизации — должна вернуться HTML-страница, а не ошибка прокси
curl -sv https://your-naive-ru-domain.example.com 2>&1 | grep -E "< HTTP|<title"
# Ожидается: HTTP/2 200 и <title>Welcome</title>
5.3 Просмотр логов
# Caddy (EU или RU сервер)
cd /opt/caddy-naive && docker compose logs -f
# naive daemon (RU сервер)
journalctl -u naive -f
# naive daemon (OpenWrt роутер)
logread -f | grep naive
5.4 Перезапуск компонентов
| Компонент | Команда |
|---|---|
| Caddy EU | cd /opt/caddy-naive && docker compose restart
|
| Caddy RU | cd /opt/caddy-naive && docker compose restart
|
| naive daemon RU | systemctl restart naive
|
| naive daemon OpenWrt | /etc/init.d/naive restart
|
Приложение: Структура файлов
EU сервер
/opt/caddy-naive/
├── Dockerfile ← сборка Caddy с плагином forwardproxy
├── docker-compose.yml ← network_mode: host, монтирование томов
├── Caddyfile ← домен, TLS, basic_auth, upstream WARP
└── www/
└── index.html ← заглушка для probe_resistance
RU сервер
/opt/caddy-naive/
├── Dockerfile
├── docker-compose.yml
├── Caddyfile ← upstream socks5://127.0.0.1:10808
└── www/
└── index.html
/usr/bin/naive ← бинарник naive daemon
/etc/naive/config.json ← конфиг: listen 10808, proxy → EU Caddy
/etc/systemd/system/naive.service ← автозапуск через systemd
/usr/local/bin/naive-users.sh ← утилита управления пользователями
OpenWrt роутер
/usr/bin/naive ← бинарник naive (архитектура роутера) /etc/naive/config.json ← конфиг: listen 1080, proxy → RU Caddy /etc/init.d/naive ← автозапуск через procd (START=95)
Приложение: Частые ошибки
| Ошибка | Причина | Решение |
|---|---|---|
requires go >= 1.25.0 при сборке |
Системный Go устарел | Dockerfile уже использует golang:1.25-alpine — ошибка не возникнет
|
wrong version number в curl через Caddy |
RU Caddy использует upstream https:// вместо SOCKS5 |
Заменить на upstream socks5://127.0.0.1:10808
|
Content-Length: 0 для HTTP GET через Caddy |
Нормальное поведение forwardproxy для не-CONNECT запросов | Не является ошибкой |
tar: invalid tar magic на OpenWrt |
BusyBox tar не поддерживает .xz | tar -xf - |
$'\r': command not found в bash-скрипте |
Windows CRLF окончания строк | Выполнить sed -i 's/\r//' /путь/к/скрипту.sh
|
| Caddy не получает сертификат | Порт 80 занят другим сервисом | grep :80, освободить порт |
| naive daemon не подключается к EU | Неверные креды или домен в config.json | Проверить journalctl -u naive, перепроверить Caddyfile EU
|
| probe_resistance возвращает ошибку прокси | Отсутствует file_server блок в Caddyfile |
Добавить file_server { root /var/www/html }
|