Mieru: каскадный шифрованный SOCKS5-туннель: различия между версиями

Материал из wolfram
Перейти к навигации Перейти к поиску
 
Строка 79: Строка 79:


=== Поток данных в одиночном режиме ===
=== Поток данных в одиночном режиме ===
[[Файл:Mieru-single-flow.png|центр|517x517px|Поток данных одиночного mieru: клиент → SOCKS5 → mieru → шифр → mita → интернет]]
[[Файл:Mieru-single-flow.png|центр|685x685px|Поток данных одиночного mieru: клиент → SOCKS5 → mieru → шифр → mita → интернет]]
=== Каскадный режим (двухзвенный) ===
=== Каскадный режим (двухзвенный) ===
В каскаде роль обычного «сервера» выполняют два узла. Первый принимает клиентов и не выходит в интернет напрямую; вместо этого он переправляет уже расшифрованный трафик во второй узел через тот же протокол mieru. Второй узел осуществляет финальный egress.
В каскаде роль обычного «сервера» выполняют два узла. Первый принимает клиентов и не выходит в интернет напрямую; вместо этого он переправляет уже расшифрованный трафик во второй узел через тот же протокол mieru. Второй узел осуществляет финальный egress.
[[Файл:Mieru-cascade-flow.png|центр|600x600пкс|Поток данных каскадного mieru: Сервер A (frontend) → Сервер B (backend) → egress]]
[[Файл:Mieru-cascade-flow.png|центр|1010x1010px|Поток данных каскадного mieru: Сервер A (frontend) → Сервер B (backend) → egress]]
=== Зачем разделять frontend и backend ===
=== Зачем разделять frontend и backend ===


Строка 115: Строка 115:


=== Формат сегмента ===
=== Формат сегмента ===
[[Файл:Mieru-segment-format.png|центр|500x500пкс|Формат сегмента mieru: padding, nonce, шифр-метаданные, шифр-полезная нагрузка]]Ключевое отличие от других протоколов: '''ВСЕ''' структурные поля сегмента (тип, размер, sequence number, ID сессии и пр.) находятся внутри поля <code>enc-meta</code> — то есть зашифрованы. Снаружи невозможно даже определить, что это за пакет, какой длины полезная нагрузка, в начале или в середине сессии находится клиент. Видны только три зоны паддинга и зашифрованные блобы.
[[Файл:Mieru-segment-format.png|центр|984x984px|Формат сегмента mieru: padding, nonce, шифр-метаданные, шифр-полезная нагрузка]]Ключевое отличие от других протоколов: '''ВСЕ''' структурные поля сегмента (тип, размер, sequence number, ID сессии и пр.) находятся внутри поля <code>enc-meta</code> — то есть зашифрованы. Снаружи невозможно даже определить, что это за пакет, какой длины полезная нагрузка, в начале или в середине сессии находится клиент. Видны только три зоны паддинга и зашифрованные блобы.


=== Traffic pattern (опциональная подгонка маскировки) ===
=== Traffic pattern (опциональная подгонка маскировки) ===
Строка 140: Строка 140:
=== Многопользовательский режим ===
=== Многопользовательский режим ===
Один сервер mita может обслуживать неограниченное число учёток одновременно. Каждый user — это пара <code>(name, password)</code>; лимит по числу одновременных сессий внутри одной учётки '''не задан в протоколе''' (можно опционально настраивать квоты по объёму трафика).
Один сервер mita может обслуживать неограниченное число учёток одновременно. Каждый user — это пара <code>(name, password)</code>; лимит по числу одновременных сессий внутри одной учётки '''не задан в протоколе''' (можно опционально настраивать квоты по объёму трафика).
[[Файл:Mieru-multi-user.png|центр|450x450пкс|Многопользовательский режим: один сервер mita обслуживает несколько учёток одновременно]]Такая модель радикально отличается от mash-/torch-протоколов с архитектурой «один сервер — один пользователь»: вам '''не нужно''' создавать отдельный контейнер на каждого пользователя.
[[Файл:Mieru-multi-user.png|центр|958x958px|Многопользовательский режим: один сервер mita обслуживает несколько учёток одновременно]]Такая модель радикально отличается от mash-/torch-протоколов с архитектурой «один сервер — один пользователь»: вам '''не нужно''' создавать отдельный контейнер на каждого пользователя.
----
----



Текущая версия от 20:22, 10 мая 2026

mieru: каскадный шифрованный SOCKS5-туннель с устойчивостью к классификации трафика

Введение

Что такое mieru

mieru (見える, «видимый») — это криптографически защищённый прокси-протокол, ориентированный на работу в средах с активным DPI и статистическим анализом сетевого трафика. В отличие от большинства современных туннелей, mieru не использует TLS и не маскируется под легитимный сайт. Вместо этого он подаёт на провод поток, который при пассивном анализе выглядит как случайные байты или произвольный текстовый протокол — без узнаваемых заголовков, фиксированных длин и характерных handshake-паттернов.

Программный пакет состоит из двух самостоятельных бинарников:

Бинарник Роль
mita Серверная часть. Принимает зашифрованные соединения, аутентифицирует пользователей, переправляет полезную нагрузку наружу
mieru Клиентская часть. Поднимает локальный SOCKS5-прокси на машине пользователя; всё, что в него пришло, шифруется и отправляется на mita

Когда применяется

  • Канал между клиентом и обычными VPN-серверами проходит через инфраструктуру с DPI или ML-классификацией протоколов.
  • Нужен альтернативный канал, не зависящий от валидного TLS-сертификата и собственного домена (mieru вообще не требует доменного имени).
  • Нужна устойчивость к активному зондированию: сервер mieru, в отличие от REALITY-/Trojan-серверов, при попытке зондирования просто молчит, не имитируя ни сайта-донора, ни TLS-handshake — нечего фингерпринтить.

Сравнение с другими методами туннелирования

Метод Транспорт Маскировка Требует домен и TLS Устойчивость к ML-классификации
WireGuard UDP нет (явный WG) нет низкая
OpenVPN UDP/TCP нет опционально низкая
Shadowsocks-AEAD-2022 TCP случайный шум нет средняя
VLESS + REALITY TCP (TLS) TLS-handshake чужого сайта да высокая (внутри пула TLS)
NaiveProxy TCP (HTTP/2) Chromium-стек, неотличим от HTTPS да высокая (внутри пула HTTPS)
Hysteria2 UDP (QUIC) QUIC поверх UDP с маскировкой под HTTPS да высокая (внутри пула QUIC)
mieru TCP / UDP, без TLS случайный шум либо текстоподобный поток (через traffic pattern) нет — только пара логин/пароль высокая (специальная подгонка энтропии и ASCII)

Принципы работы

Поток данных в одиночном режиме

Поток данных одиночного mieru: клиент → SOCKS5 → mieru → шифр → mita → интернет
Поток данных одиночного mieru: клиент → SOCKS5 → mieru → шифр → mita → интернет

Каскадный режим (двухзвенный)

В каскаде роль обычного «сервера» выполняют два узла. Первый принимает клиентов и не выходит в интернет напрямую; вместо этого он переправляет уже расшифрованный трафик во второй узел через тот же протокол mieru. Второй узел осуществляет финальный egress.

Поток данных каскадного mieru: Сервер A (frontend) → Сервер B (backend) → egress
Поток данных каскадного mieru: Сервер A (frontend) → Сервер B (backend) → egress

Зачем разделять frontend и backend

  • Адрес backend-сервера никогда не виден клиентскому ПО. Если frontend замечен и заблокирован — backend остаётся неизвестным наблюдателям и переиспользуется со следующим frontend.
  • Frontend хранит только учётки клиентов и не имеет финального egress. Компрометация его конфига не раскрывает финальный IP.
  • Двойное шифрование mieru на каждом плече: каждый сегмент дважды проходит AEAD с разными ключами, что усложняет статистический анализ.

Шифрование и аутентификация

Параметр Значение
Алгоритм XChaCha20-Poly1305 (AEAD) с 24-байтным nonce
Длина ключа 32 байта
Метод выработки ключа PBKDF2-SHA256, 64 итерации, соль = SHA-256 от текущего времени, округлённого до 2 минут
Идентификатор пользователя в пакете Последние 4 байта nonce заменены на префикс SHA-256 от username + nonce[0:16]; имя пользователя на провод не передаётся
Защита от reuse Проверка nonce-кэша на серверной стороне; повторный пакет отбрасывается

Ключ шифрования не передаётся через канал и нигде не хранится в виде сериализованной строки: он каждый раз генерируется на лету из пары логин+пароль и текущего времени. Из этого следует одно практическое требование, которое легко не заметить.

Требование к синхронизации часов

Расхождение системного времени клиента и сервера не должно превышать 4 минуты. При большем расхождении ключ, выработанный клиентом, не сойдётся с ключом сервера, и подключения будут молча падать (без явной ошибки в логах). На обоих серверах и на клиентском устройстве должен работать NTP-демон.

Формат сегмента

Формат сегмента mieru: padding, nonce, шифр-метаданные, шифр-полезная нагрузка
Формат сегмента mieru: padding, nonce, шифр-метаданные, шифр-полезная нагрузка

Ключевое отличие от других протоколов: ВСЕ структурные поля сегмента (тип, размер, sequence number, ID сессии и пр.) находятся внутри поля enc-meta — то есть зашифрованы. Снаружи невозможно даже определить, что это за пакет, какой длины полезная нагрузка, в начале или в середине сессии находится клиент. Видны только три зоны паддинга и зашифрованные блобы.

Traffic pattern (опциональная подгонка маскировки)

mieru предоставляет два независимых механизма для подмены статистических признаков шифр-трафика на «обычные»:

Опция Что делает Накладные расходы
tcpFragment Дробит TCP-пакеты на мелкие куски и вставляет случайную задержку между ними. Цель — сломать узнаваемость «один большой шифр-стрим» +латентность до maxSleepMs мс на каждый фрагмент
nonce.NONCE_TYPE_PRINTABLE Подменяет первые N байт nonce печатными ASCII-символами. Цель — увести классификатор «высокая энтропия от нулевого байта» в ложное «это текстовый протокол» почти ноль (только подмена байтов)
nonce.NONCE_TYPE_FIXED Подменяет первые байты nonce фиксированными hex-строками из списка. Цель — имитировать конкретный фиксированный заголовок ноль

Включение traffic pattern имеет смысл только на видимом анализатором плече (клиент → frontend). На внутреннем плече каскада (frontend → backend) это лишний overhead.

Многопользовательский режим

Один сервер mita может обслуживать неограниченное число учёток одновременно. Каждый user — это пара (name, password); лимит по числу одновременных сессий внутри одной учётки не задан в протоколе (можно опционально настраивать квоты по объёму трафика).

Многопользовательский режим: один сервер mita обслуживает несколько учёток одновременно
Многопользовательский режим: один сервер mita обслуживает несколько учёток одновременно

Такая модель радикально отличается от mash-/torch-протоколов с архитектурой «один сервер — один пользователь»: вам не нужно создавать отдельный контейнер на каждого пользователя.


Что должно быть на серверах перед установкой

Минимальная конфигурация каждого сервера

Требование Зачем
1 Linux дистрибутив (Ubuntu 22.04+, Debian 12+, Fedora 38+ или сравнимый) Среда выполнения Docker
2 SSH-доступ с правами root либо аккаунт с sudo без пароля Установка пакетов и управление контейнерами
3 Docker Engine 24.0+ и плагин Docker Compose v2.20+ Запуск контейнеров mita и mieru
4 NTP-демон активен (chrony или systemd-timesyncd) Синхронизация часов в пределах 4 минут (см. раздел «Шифрование»)
5 Открытый наружу TCP-диапазон (для нашей статьи — 2012-2022) Принимающий порт mita

Опциональная конфигурация: egress через сторонний прокси

По умолчанию backend-сервер выходит в интернет напрямую с публичного IP виртуальной машины. В качестве промежуточной цепочки часто используется выделенный SOCKS5-прокси, обслуживаемый сторонней панелью (например, 3x-ui над xray-core).

Параметр Пример Комментарий
1 SOCKS5-inbound, слушающий только на 172.17.0.1 (docker0-bridge gateway) порт 24366 Слушать на 127.0.0.1 не подойдёт: контейнер mita с network_mode: host увидит нужный адрес именно как 172.17.0.1
2 Аутентификация на этом inbound отключена Можно включить и прописать креды в egress.proxies mita, но без авторизации проще
3 Routing-rule: трафик с этого inbound → нужный outbound (например, WireGuard-к-WARP, наружный VPN, сторонний прокси) Зависит от вашей панели; в 3x-ui — раздел «Маршрутизация»

Проверка egress-цепочки

Команда выполняется на самом backend-сервере. Заменить 24366 на ваш порт.

docker run --rm curlimages/curl:latest --max-time 12 --socks5 172.17.0.1:24366 https://icanhazip.com

Ожидание: IP вашего egress-узла (например, IP Cloudflare WARP или IP стороннего VPN). Если возвращается публичный IP вашей VM — egress не работает, проверьте routing панели.

Если egress через сторонний прокси не нужен (готовы выходить с публичного IP backend-сервера) — пропустите этот раздел; в дальнейших шагах указания «без egress» помечены явно.


Параметры, которые нужно подготовить заранее

Перед началом установки заполните таблицу собственными значениями. На каждом шаге, где встретится плейсхолдер, мы будем ссылаться на эту таблицу.

Плейсхолдер Что это Пример
YOUR_FRONTEND_HOST Адрес frontend-сервера для SSH (IP, имя из ~/.ssh/config либо домен) 198.51.100.10
YOUR_BACKEND_HOST Адрес backend-сервера для SSH 203.0.113.20
YOUR_BACKEND_PUBLIC_IP Публичный IP backend-сервера, к которому будет коннектиться mieru-client с frontend 203.0.113.20
YOUR_FRONTEND_PUBLIC_IP Публичный IP frontend-сервера, к которому будут коннектиться клиенты 198.51.100.10
YOUR_BRIDGE_PASSWORD Пароль учётки mieru, которой mieru-client на frontend подключается к mita на backend. Один на оба узла длинная случайная строка, сгенерированная openssl rand
YOUR_CLIENT_PASSWORD Пароль первой клиентской учётки client01 на frontend длинная случайная строка
YOUR_EGRESS_HOST Адрес SOCKS5-egress на backend (если используете) 172.17.0.1
YOUR_EGRESS_PORT Порт SOCKS5-egress 24366

Сохраните таблицу в надёжное место — особенно пароли. Без них восстановить рабочее состояние нельзя.


Установка backend-сервера (точка выхода)

Все команды этого раздела выполняются на backend-сервере по SSH. Каждое пояснение — одна команда.

Шаг 1. Подключение по SSH

ssh root@YOUR_BACKEND_HOST

Шаг 2. Проверка Docker

docker --version

Если выведено Docker version 24.0.x или новее — переходите к шагу 3. Если command not found — установите Docker по официальной инструкции для вашей ОС: https://docs.docker.com/engine/install/. После установки вернитесь сюда. Дополнительно проверьте плагин Compose.

docker compose version

Шаг 3. Создание рабочего каталога

mkdir -p /opt/mieru-backend
cd /opt/mieru-backend

Шаг 4. Создание Dockerfile

Откройте файл для редактирования.

nano /opt/mieru-backend/Dockerfile

Вставьте следующее содержимое полностью.

ARG MIERU_VERSION=3.32.0
ARG TARGETARCH=amd64

FROM alpine:3.19 AS downloader
ARG MIERU_VERSION
ARG TARGETARCH
RUN apk add --no-cache ca-certificates wget
WORKDIR /tmp/mita
RUN wget -qO- "https://github.com/enfein/mieru/releases/download/v${MIERU_VERSION}/mita_${MIERU_VERSION}_linux_${TARGETARCH}.tar.gz" | tar xz
WORKDIR /tmp/mieru
RUN wget -qO- "https://github.com/enfein/mieru/releases/download/v${MIERU_VERSION}/mieru_${MIERU_VERSION}_linux_${TARGETARCH}.tar.gz" | tar xz

FROM alpine:3.19
RUN apk add --no-cache ca-certificates tini && \
    adduser -H -D -g "" mita && \
    adduser -H -D -g "" mieru
COPY --from=downloader /tmp/mita/mita /usr/local/bin/mita
COPY --from=downloader /tmp/mieru/mieru /usr/local/bin/mieru
RUN chmod +x /usr/local/bin/mita /usr/local/bin/mieru && \
    mkdir -p /etc/mita /var/lib/mita /var/run/mita \
             /etc/mieru /var/lib/mieru /var/run/mieru /srv && \
    chown -R mita:mita /etc/mita /var/lib/mita /var/run/mita && \
    chown -R mieru:mieru /etc/mieru /var/lib/mieru /var/run/mieru
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["/sbin/tini","--","/usr/local/bin/docker-entrypoint.sh"]

Сохраните: Ctrl+O, Enter, Ctrl+X.

Шаг 5. Создание скрипта-entrypoint

nano /opt/mieru-backend/docker-entrypoint.sh

Вставьте полное содержимое.

#!/bin/sh
set -eu

MODE="${MIERU_MODE:-mita}"
CONFIG_FILE="${MIERU_CONFIG:-/srv/config.json}"

case "$MODE" in
    mita)  BIN=mita ;;
    mieru) BIN=mieru ;;
    *) echo "[entrypoint] FATAL: MIERU_MODE='$MODE' (mita или mieru)"; exit 1 ;;
esac

if [ ! -f "$CONFIG_FILE" ]; then
    echo "[entrypoint] WARN: $CONFIG_FILE не найден"
fi

if [ "$MODE" = "mieru" ]; then
    if [ -f "$CONFIG_FILE" ]; then
        mieru apply config "$CONFIG_FILE" 2>&1
    fi
    exec mieru run
else
    apply_via_rpc() {
        i=0
        while [ "$i" -lt 30 ]; do
            i=$((i+1)); sleep 1
            if [ -f "$CONFIG_FILE" ] && mita apply config "$CONFIG_FILE" >/tmp/apply.out 2>&1; then
                mita start >/tmp/start.out 2>&1
                return 0
            fi
        done
    }
    apply_via_rpc &
    exec mita run
fi

Сохраните: Ctrl+O, Enter, Ctrl+X. Дайте файлу права на исполнение.

chmod +x /opt/mieru-backend/docker-entrypoint.sh

Шаг 6. Генерация пароля для bridge-учётки

openssl rand -base64 24 | tr -d '=/+' | head -c 31

Команда выведет одну строку из 31 символа. Скопируйте её и впишите в подготовленную таблицу как YOUR_BRIDGE_PASSWORD. Этот же пароль понадобится на frontend-сервере (шаг ниже).

Шаг 7. Создание серверного конфига

nano /opt/mieru-backend/server.json

Вставьте содержимое и замените YOUR_BRIDGE_PASSWORD на скопированное значение из шага 6. Если вы используете egress через сторонний SOCKS5-прокси — оставьте секцию egress как есть и при необходимости подправьте host и port. Если не используете — удалите всю секцию egress от открывающей до закрывающей фигурной скобки.

{
    "portBindings": [
        {"portRange": "2012-2022", "protocol": "TCP"}
    ],
    "users": [
        {"name": "frontend-bridge", "password": "YOUR_BRIDGE_PASSWORD"}
    ],
    "loggingLevel": "INFO",
    "mtu": 1380,
    "egress": {
        "proxies": [
            {
                "name": "external-egress",
                "protocol": "SOCKS5_PROXY_PROTOCOL",
                "host": "172.17.0.1",
                "port": 24366
            }
        ],
        "rules": [
            {
                "ipRanges": ["*"],
                "domainNames": ["*"],
                "action": "PROXY",
                "proxyNames": ["external-egress"]
            }
        ]
    }
}

Сохраните: Ctrl+O, Enter, Ctrl+X. Защитите файл (содержит пароль).

chmod 600 /opt/mieru-backend/server.json

Шаг 8. Создание docker-compose.yml

nano /opt/mieru-backend/docker-compose.yml

Вставьте.

services:
  mita:
    build:
      context: .
      args:
        MIERU_VERSION: "3.32.0"
    image: mieru-bundle:3.32.0
    container_name: mita-backend
    restart: unless-stopped
    network_mode: host
    environment:
      MIERU_MODE: mita
      MIERU_CONFIG: /srv/server.json
    volumes:
      - ./server.json:/srv/server.json:ro
      - mita-state:/etc/mita
      - mita-runtime:/var/run/mita
      - mita-data:/var/lib/mita
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  mita-state:
  mita-runtime:
  mita-data:

Сохраните: Ctrl+O, Enter, Ctrl+X.

Шаг 9. Сборка образа

docker compose -f /opt/mieru-backend/docker-compose.yml build

Сборка длится 1–3 минуты. По завершении выводится Image mieru-bundle:3.32.0 Built.

Шаг 10. Запуск

docker compose -f /opt/mieru-backend/docker-compose.yml up -d

Подождите 10 секунд.

sleep 10

Проверьте логи.

docker logs mita-backend --tail 25

Ожидаемые строки:

[entrypoint] config применён (/srv/server.json)
[entrypoint] mita start: OK
mita server status is "RUNNING"

Проверьте, что сервер слушает порты 2012-2022.

ss -tlnp | grep -E ':20(1[2-9]|2[012]) '

Должны появиться 11 строк со статусом LISTEN и пользователем mita. Если их нет — обратитесь к разделу «Поиск проблем».


Установка frontend-сервера (точка входа)

Все команды этого раздела выполняются на frontend-сервере по SSH.

Шаг 1. Подключение и подготовка

ssh root@YOUR_FRONTEND_HOST
mkdir -p /opt/mieru-frontend && cd /opt/mieru-frontend

Шаг 2. Dockerfile и entrypoint (одинаковые с backend)

Создайте оба файла так же, как на backend-сервере (см. шаги 4–5 раздела «Установка backend-сервера»). Содержимое идентично — образ универсальный, режим работы выбирается переменной окружения.

nano /opt/mieru-frontend/Dockerfile

(вставьте тот же Dockerfile, что на backend)

nano /opt/mieru-frontend/docker-entrypoint.sh

(вставьте тот же entrypoint)

chmod +x /opt/mieru-frontend/docker-entrypoint.sh

Шаг 3. Генерация пароля первой клиентской учётки

openssl rand -base64 24 | tr -d '=/+' | head -c 31

Скопируйте результат и впишите в таблицу как YOUR_CLIENT_PASSWORD. Это пароль учётки client01, который вы потом отдадите своему первому клиентскому устройству.

Шаг 4. Серверный конфиг (mita-frontend)

nano /opt/mieru-frontend/server.json

Замените YOUR_CLIENT_PASSWORD на значение из шага 3.

{
    "portBindings": [
        {"portRange": "2012-2022", "protocol": "TCP"}
    ],
    "users": [
        {"name": "client01", "password": "YOUR_CLIENT_PASSWORD"}
    ],
    "loggingLevel": "INFO",
    "mtu": 1380,
    "trafficPattern": {
        "unlockAll": false,
        "tcpFragment": {"enable": true, "maxSleepMs": 10},
        "nonce": {
            "type": "NONCE_TYPE_PRINTABLE",
            "minLen": 6,
            "maxLen": 8
        }
    },
    "egress": {
        "proxies": [
            {
                "name": "to-backend",
                "protocol": "SOCKS5_PROXY_PROTOCOL",
                "host": "127.0.0.1",
                "port": 1080
            }
        ],
        "rules": [
            {
                "ipRanges": ["*"],
                "domainNames": ["*"],
                "action": "PROXY",
                "proxyNames": ["to-backend"]
            }
        ]
    }
}

Сохраните и защитите файл.

chmod 600 /opt/mieru-frontend/server.json

Шаг 5. Клиентский конфиг (mieru-client)

nano /opt/mieru-frontend/client.json

Замените YOUR_BACKEND_PUBLIC_IP на публичный IP backend-сервера, YOUR_BRIDGE_PASSWORD — на пароль bridge-учётки, который вы создавали на backend в шаге 6.

{
    "profiles": [
        {
            "profileName": "default",
            "user": {
                "name": "frontend-bridge",
                "password": "YOUR_BRIDGE_PASSWORD"
            },
            "servers": [
                {
                    "ipAddress": "YOUR_BACKEND_PUBLIC_IP",
                    "portBindings": [
                        {"portRange": "2012-2022", "protocol": "TCP"}
                    ]
                }
            ],
            "mtu": 1380,
            "multiplexing": {"level": "MULTIPLEXING_HIGH"},
            "handshakeMode": "HANDSHAKE_NO_WAIT"
        }
    ],
    "activeProfile": "default",
    "rpcPort": 8964,
    "socks5Port": 1080,
    "loggingLevel": "INFO",
    "socks5ListenLAN": false,
    "httpProxyListenLAN": false
}

Сохраните и защитите файл.

chmod 600 /opt/mieru-frontend/client.json

Шаг 6. docker-compose.yml на frontend

nano /opt/mieru-frontend/docker-compose.yml

Вставьте.

services:
  mieru-client:
    build:
      context: .
      args:
        MIERU_VERSION: "3.32.0"
    image: mieru-bundle:3.32.0
    container_name: mieru-client
    restart: unless-stopped
    network_mode: host
    environment:
      MIERU_MODE: mieru
      MIERU_CONFIG: /srv/client.json
    volumes:
      - ./client.json:/srv/client.json:ro
      - mieru-client-state:/etc/mieru
      - mieru-client-runtime:/var/run/mieru
      - mieru-client-data:/var/lib/mieru
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  mita:
    image: mieru-bundle:3.32.0
    container_name: mita-frontend
    restart: unless-stopped
    network_mode: host
    depends_on:
      - mieru-client
    environment:
      MIERU_MODE: mita
      MIERU_CONFIG: /srv/server.json
    volumes:
      - ./server.json:/srv/server.json:ro
      - mita-state:/etc/mita
      - mita-runtime:/var/run/mita
      - mita-data:/var/lib/mita
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  mieru-client-state:
  mieru-client-runtime:
  mieru-client-data:
  mita-state:
  mita-runtime:
  mita-data:

Сохраните.

Шаг 7. Сборка и запуск

docker compose -f /opt/mieru-frontend/docker-compose.yml build
docker compose -f /opt/mieru-frontend/docker-compose.yml up -d
sleep 12

Шаг 8. Проверка

docker logs mieru-client --tail 10

Должна быть строка mieru: config применён и затем запуск mieru run.

docker logs mita-frontend --tail 10

Должно быть RUNNING и mita start: OK.

docker exec mieru-client mieru status

Ожидание: mieru client is running.

docker exec mita-frontend mita status

Ожидание: mita server status is "RUNNING".

Шаг 9. Сквозной тест каскада

С frontend-сервера обратитесь во внешний интернет через локальный SOCKS5 mieru-client. Это эквивалент того, как пойдёт реальный клиентский запрос.

curl -sS --max-time 30 --proxy socks5h://127.0.0.1:1080 https://icanhazip.com

Ожидание: IP вашего egress-узла. Например, IP Cloudflare WARP, если на backend настроен такой outbound. Команда не должна возвращать публичный IP frontend-сервера. Контрольный замер прямого IP frontend для сравнения.

curl -sS --max-time 5 https://icanhazip.com

Если первый IP отличается от второго — каскад работает.


Подключение клиентов

mieru имеет два равнозначных формата отдачи параметров клиенту: Clash/Mihomo YAML и mierus:// URI. На практике YAML более переносим — его понимает большинство клиентов в одном виде, тогда как URI разбирается каждым приложением по-своему.

Десктоп: Clash Verge Rev / Mihomo Party / NekoBox

Создайте новый профиль (или отредактируйте существующий) и добавьте блок proxies:

proxies:
  - name: mieru-frontend
    type: mieru
    server: YOUR_FRONTEND_PUBLIC_IP
    port: 2022
    username: client01
    password: YOUR_CLIENT_PASSWORD
    multiplexing: MULTIPLEXING_HIGH

Параметр port: 2022 — это один из портов из диапазона 2012-2022; mita слушает все 11 параллельно, клиент использует один (мультиплексор).

Мобильные: Karing / ClashMi / NekoBox+plugin / husi

Подключение через тот же YAML-блок выше — большинство клиентов поддерживают вставку YAML вручную в раздел «Серверы» или «Outbounds». Сценарий импорта URI в Karing 1.2.x работает плохо (поле порта обнуляется), поэтому YAML предпочтительнее.

Универсальный URI-формат

mierus://client01:YOUR_CLIENT_PASSWORD@YOUR_FRONTEND_PUBLIC_IP?port=2022&protocol=TCP&mtu=1380&multiplexing=MULTIPLEXING_HIGH&handshake-mode=HANDSHAKE_NO_WAIT

Подходит для нативного mieru CLI, husi с mieru-плагином, Exclave.


Подключение клиента на OpenWrt-роутере

Если на роутере уже стоит трафик-менеджер (например, podkop), и вы хотите, чтобы он отправлял трафик через mieru — поставьте на роутер standalone mieru-клиент с локальным SOCKS5, и подкоп цепляйте к этому SOCKS5.

Шаг 1. Подключение к роутеру

ssh root@YOUR_ROUTER_HOST

Шаг 2. Скачивание бинаря

Архитектура роутера большинства современных моделей — aarch64. Если ваш роутер — другой (например, mips), проверьте список релизов https://github.com/enfein/mieru/releases/latest и подставьте нужный артефакт в команду.

cd /tmp && wget -q https://github.com/enfein/mieru/releases/download/v3.32.0/mieru_3.32.0_linux_arm64.tar.gz -O mieru.tar.gz
tar xzf mieru.tar.gz && mv mieru /usr/bin/mieru && chmod +x /usr/bin/mieru && rm -f /tmp/mieru.tar.gz /tmp/LICENSE /tmp/README.md
/usr/bin/mieru version

Должно вывести 3.32.0.

Шаг 3. Клиентский конфиг

nano /etc/mieru_client_config.json

Вставьте, заменив плейсхолдеры:

{
    "profiles": [
        {
            "profileName": "default",
            "user": {
                "name": "client01",
                "password": "YOUR_CLIENT_PASSWORD"
            },
            "servers": [
                {
                    "ipAddress": "YOUR_FRONTEND_PUBLIC_IP",
                    "portBindings": [
                        {"portRange": "2012-2022", "protocol": "TCP"}
                    ]
                }
            ],
            "mtu": 1380,
            "multiplexing": {"level": "MULTIPLEXING_HIGH"},
            "handshakeMode": "HANDSHAKE_NO_WAIT",
            "trafficPattern": {
                "unlockAll": false,
                "tcpFragment": {"enable": true, "maxSleepMs": 10},
                "nonce": {
                    "type": "NONCE_TYPE_PRINTABLE",
                    "minLen": 6,
                    "maxLen": 8
                }
            }
        }
    ],
    "activeProfile": "default",
    "rpcPort": 8964,
    "socks5Port": 1090,
    "loggingLevel": "ERROR",
    "socks5ListenLAN": false,
    "httpProxyListenLAN": false
}

Сохраните и защитите файл.

chmod 600 /etc/mieru_client_config.json

Ключевые отличия конфига для роутера от десктопного клиента:

  • socks5Port: 1090 — порт 1080 часто занят другими прокси (например, naive); 1090 свободен.
  • loggingLevel: "ERROR" — overlay на роутерах маленький, ограничение логов защищает от заполнения диска.
  • trafficPattern включён полностью — на пути «роутер → frontend» классификатор смотрит, маскировка нужна.

Шаг 4. Init.d-скрипт

OpenWrt использует procd; в отличие от mita, на OpenWrt mieru читает JSON-конфиг напрямую через переменную MIERU_CONFIG_JSON_FILE, и команды mieru apply config и mieru describe config не используются.

nano /etc/init.d/mieru

Вставьте.

#!/bin/sh /etc/rc.common

START=99
STOP=01

MIERU_BIN="/usr/bin/mieru"
MIERU_CONFIG="/etc/mieru_client_config.json"
PID_FILE="/var/run/mieru.pid"

start() {
    echo "Starting mieru client..."
    export MIERU_CONFIG_JSON_FILE=$MIERU_CONFIG
    start-stop-daemon -S -b -m -p "$PID_FILE" -x "$MIERU_BIN" -- run
    RC=$?
    if [ $RC -eq 0 ]; then
        echo "mieru client is started"
        exit 0
    else
        echo "failed to start mieru client"
        exit $RC
    fi
}

stop() {
    echo "Stopping mieru client..."
    start-stop-daemon -K -p "$PID_FILE"
    RC=$?
    if [ $RC -eq 0 ]; then
        echo "mieru client is stopped"
        rm -f "$PID_FILE"
    else
        echo "failed to stop mieru client"
    fi
}

Сохраните и сделайте исполняемым.

chmod +x /etc/init.d/mieru

Шаг 5. Запуск и автозапуск

/etc/init.d/mieru enable
/etc/init.d/mieru start

Шаг 6. Проверка

ps | grep mieru | grep -v grep

Должна быть одна строка с процессом /usr/bin/mieru run.

netstat -tlnp 2>/dev/null | grep 1090

Должна быть строка 127.0.0.1:1090 ... LISTEN ... mieru.

curl -sS --max-time 30 -x socks5h://127.0.0.1:1090 https://icanhazip.com

Должен вернуть IP egress-узла каскада.

Шаг 7. Подключение podkop к локальному mieru

В LuCI откройте подкоп. Создайте новую секцию или измените существующую:

Поле Значение
Тип конфига URL
Proxy string socks5://127.0.0.1:1090

Эквивалент через uci:

uci set podkop.10=section
uci set podkop.10.connection_type='proxy'
uci set podkop.10.proxy_config_type='url'
uci set podkop.10.proxy_string='socks5://127.0.0.1:1090'
uci commit podkop
service podkop restart

Подкоп отправляет mieru-серверу обычный SOCKS5-запрос на 127.0.0.1:1090; mieru шифрует его и отправляет через каскад.


Управление пользователями: скрипт mieru-users.sh

Зачем он нужен

mita-сервер хранит список учёток в файле server.json. Чтобы добавлять/удалять/смотреть пользователей вручную каждый раз — много шагов: jq-правка JSON, вызов mita apply config, генерация ссылки клиенту, опционально mita delete user для разрыва активных сессий. Скрипт mieru-users.sh автоматизирует это всё.

Что делает скрипт

Пункт меню Действие
1. Список пользователей Чтение server.json + опрос mita get connections для активных сессий
2. Добавить пользователя Запрос имени, генерация пароля через openssl rand, бэкап JSON, jq-вставка, mita apply config, выдача готовых YAML и URI клиенту
3. Удалить пользователя mita delete user для разрыва сессий → jq-удаление из JSON → apply config
4. Показать URI клиента Сборка YAML и URI по выбранному имени
5. Статус сервера mita status + get connections + get metrics

Установка скрипта

Все команды выполняются на frontend-сервере по SSH.

Шаг 1. Загрузка скрипта

nano /usr/local/bin/mieru-users.sh

Вставьте полное содержимое скрипта (исходный код в спойлере «Исходный код mieru-users.sh» в конце статьи).

Сохраните: Ctrl+O, Enter, Ctrl+X.

Шаг 2. Подстановка реального адреса

В начале скрипта есть строка SERVER_IP="YOUR_FRONTEND_PUBLIC_IP". Замените её на реальный IP frontend-сервера.

sed -i 's/YOUR_FRONTEND_PUBLIC_IP/198.51.100.10/' /usr/local/bin/mieru-users.sh

(подставьте свой IP вместо 198.51.100.10).

Шаг 3. Права на исполнение

chmod +x /usr/local/bin/mieru-users.sh

Шаг 4. Проверка

bash -n /usr/local/bin/mieru-users.sh && echo OK

Если выводит OK — скрипт установлен корректно.

Использование

sudo bash /usr/local/bin/mieru-users.sh

Появится меню:

╔══════════════════════════════════════╗
║   mieru User Manager                 ║
║   IP: 198.51.100.10                  ║
╚══════════════════════════════════════╝

  Пользователей в конфиге: 1

  1. Список пользователей и активных сессий
  2. Добавить пользователя
  3. Удалить пользователя
  4. Показать URI клиента
  5. Статус сервера
  0. Выход

После добавления пользователя скрипт выводит готовые конфиги:

=== Вариант 1: Clash / Mihomo YAML (рекомендую) === proxies:

 - name: mieru-RU-alice
   type: mieru
   server: 198.51.100.10
   port: 2022
   username: alice
   password: <автогенерированный>
   multiplexing: MULTIPLEXING_HIGH

=== Вариант 2: mierus:// URI === mierus://alice:<пароль>@198.51.100.10?port=2022&protocol=TCP&mtu=1380&multiplexing=MULTIPLEXING_HIGH&handshake-mode=HANDSHAKE_NO_WAIT


Команды управления

На обоих серверах

Посмотреть логи в реальном времени.

docker logs -f mita-backend       # на backend
docker logs -f mita-frontend mieru-client       # на frontend

Перезапустить.

docker compose -f /opt/mieru-backend/docker-compose.yml restart    # на backend
docker compose -f /opt/mieru-frontend/docker-compose.yml restart   # на frontend

Остановить.

docker compose -f /opt/mieru-backend/docker-compose.yml down       # на backend
docker compose -f /opt/mieru-frontend/docker-compose.yml down      # на frontend

Обновить версию mita/mieru. Поменяйте 3.32.0 на нужную в docker-compose.yml и пересоберите.

docker compose -f /opt/mieru-backend/docker-compose.yml build && docker compose -f /opt/mieru-backend/docker-compose.yml up -d

Команды mita через CLI (внутри контейнера)

docker exec mita-backend mita status              # IDLE / RUNNING
docker exec mita-backend mita get users           # список user'ов и их статистика
docker exec mita-backend mita get connections     # активные сессии
docker exec mita-backend mita get metrics         # метрики
docker exec mita-backend mita describe config     # текущий применённый конфиг

На OpenWrt-роутере

/etc/init.d/mieru start | stop | restart
ps | grep mieru | grep -v grep
logread | grep mieru

Поиск проблем

Симптом: сервер показывает IDLE, порты не слушаются

mita после apply config остаётся в IDLE до явной команды mita start. В нашем entrypoint это происходит автоматически, но если что-то пошло не так, выполните вручную внутри контейнера:

docker exec mita-backend mita start

Симптом: FATAL: getUid("mita") failed

В образ не добавлен системный пользователь mita. Проверьте, что Dockerfile содержит строки adduser -H -D -g "" mita и adduser -H -D -g "" mieru, пересоберите образ.

Симптом: клиент подключается, но трафик не идёт

Проверьте на клиенте время системы. Расхождение более 4 минут с сервером приведёт к молчаливому отказу. Команда на Linux/macOS:

date -u

Если время существенно расходится — настройте NTP.

Симптом: на frontend curl возвращает не egress-IP, а frontend-IP

Trafic идёт мимо mieru-client. Возможные причины:

  • egress.action в frontend-конфиге — DIRECT вместо PROXY.
  • mieru-client не запущен либо упал. Проверьте: docker ps.
  • Backend не доступен. Проверьте на frontend: nc -vz YOUR_BACKEND_PUBLIC_IP 2022.

Симптом: на frontend curl возвращает frontend-IP, а должен — backend-IP

То же самое, что выше: трафик не доходит до backend. Дополнительно посмотрите логи:

docker logs mieru-client --tail 50

Ошибки TLS handshake здесь невозможны — mieru не использует TLS. Ищите auth failed (неверная пара логин/пароль на mita-backend) и connection refused (backend не слушает).

Симптом: на роутере Karing/Clash подключается, но трафик не идёт

В первую очередь проверить YAML-конфиг: тип type: mieru (не type: socks5!), порт port: 2022 (одиночное число, не диапазон). Многие mihomo-форки не парсят диапазон портов корректно.


Исходный код mieru-users.sh

Полный текст скрипта приведён ниже. Скопируйте его целиком в /usr/local/bin/mieru-users.sh на frontend-сервере по инструкции выше.

#!/bin/bash
# mieru-users — управление пользователями mita
# Источник истины: /opt/mieru-frontend/server.json
# Применение через mita apply config (RPC к работающему daemon).

CONFIG_FILE="/opt/mieru-frontend/server.json"
COMPOSE_DIR="/opt/mieru-frontend"
COMPOSE_SVC="mita"
BACKUP_DIR="/opt/mieru-frontend/backups"
BACKUP_KEEP=10
SERVER_IP="YOUR_FRONTEND_PUBLIC_IP"

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 mieru-users.sh${NC}"; exit 1
    fi
}

check_deps() {
    for c in jq docker openssl; do
        if ! command -v "$c" >/dev/null 2>&1; then
            echo -e "${RED}Не найдено: $c${NC}"; exit 1
        fi
    done
    if [[ ! -f "$CONFIG_FILE" ]]; then
        echo -e "${RED}Не найден $CONFIG_FILE${NC}"; exit 1
    fi
}

mita_cli() {
    docker compose -f "$COMPOSE_DIR/docker-compose.yml" exec -T "$COMPOSE_SVC" mita "$@"
}

backup_config() {
    mkdir -p "$BACKUP_DIR"
    local stamp; stamp=$(date +%Y%m%d-%H%M%S)
    cp "$CONFIG_FILE" "$BACKUP_DIR/server-$stamp.json"
    ls -1t "$BACKUP_DIR"/server-*.json 2>/dev/null | tail -n +$((BACKUP_KEEP+1)) | xargs -r rm -f
}

apply_config() {
    if ! mita_cli apply config /srv/server.json 2>&1; then
        echo -e "${RED}mita apply config упал. Откатываюсь к последнему бэкапу.${NC}"
        local last; last=$(ls -1t "$BACKUP_DIR"/server-*.json 2>/dev/null | head -1)
        if [[ -n "$last" ]]; then
            cp "$last" "$CONFIG_FILE"
            mita_cli apply config /srv/server.json >/dev/null 2>&1 || true
        fi
        return 1
    fi
    return 0
}

press_enter() { echo ""; read -rp "Enter для возврата..."; }

generate_password() {
    openssl rand -base64 24 | tr -d '=/+' | head -c 31
}

generate_uri() {
    echo "mierus://${1}:${2}@${SERVER_IP}?port=2022&protocol=TCP&mtu=1380&multiplexing=MULTIPLEXING_HIGH&handshake-mode=HANDSHAKE_NO_WAIT"
}

generate_yaml() {
    cat <<EOF
proxies:
  - name: mieru-${1}
    type: mieru
    server: ${SERVER_IP}
    port: 2022
    username: ${1}
    password: ${2}
    multiplexing: MULTIPLEXING_HIGH
EOF
}

print_uri_for() {
    echo ""
    echo -e "${CYAN}=== Вариант 1: Clash / Mihomo YAML ===${NC}"
    echo -e "${BOLD}$(generate_yaml "$1" "$2")${NC}"
    echo ""
    echo -e "${CYAN}=== Вариант 2: mierus:// URI ===${NC}"
    echo -e "${BOLD}  $(generate_uri "$1" "$2")${NC}"
    echo ""
    echo -e "${CYAN}user=${BOLD}${1}${NC}, pwd=${BOLD}${2}${NC}, host=${BOLD}${SERVER_IP}${NC}, port=${BOLD}2022${NC}"
}

list_users() {
    echo ""
    local n; n=$(jq '.users | length' "$CONFIG_FILE")
    echo -e "${BOLD}${CYAN}=== Пользователей: $n ===${NC}"
    jq -r '.users[] | "  \(.name)\t\(.password[0:6])…"' "$CONFIG_FILE" | nl -w2 -s'. '
    echo ""
    echo -e "${BOLD}${CYAN}=== Активные сессии ===${NC}"
    mita_cli get connections 2>&1 | head -20
    press_enter
}

add_user() {
    echo ""; read -rp "Имя пользователя: " name
    name=$(echo "$name" | tr -d ' ')
    [[ -z "$name" ]] && { echo "Пусто."; press_enter; return; }
    if jq -e --arg n "$name" '.users[] | select(.name == $n)' "$CONFIG_FILE" >/dev/null; then
        echo "Уже есть."; press_enter; return
    fi
    read -rp "Пароль (Enter — сгенерировать): " pwd
    [[ -z "$pwd" ]] && pwd=$(generate_password)

    backup_config
    local tmp; tmp=$(mktemp)
    jq --arg n "$name" --arg p "$pwd" '.users += [{"name":$n,"password":$p}]' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"

    if apply_config; then
        echo -e "${GREEN}Пользователь $name добавлен.${NC}"
        print_uri_for "$name" "$pwd"
    fi
    press_enter
}

delete_user() {
    mapfile -t users < <(jq -r '.users[].name' "$CONFIG_FILE")
    [[ ${#users[@]} -eq 0 ]] && { echo "Пусто."; press_enter; return; }
    for i in "${!users[@]}"; do echo "  $((i+1))) ${users[$i]}"; done
    echo "  0) Отмена"; read -rp "Номер: " n
    [[ "$n" == "0" || -z "$n" ]] && return
    local target="${users[$((n-1))]}"
    read -rp "Удалить '$target'? (y/N): " yn
    [[ ! "$yn" =~ ^[Yy]$ ]] && return

    mita_cli delete user "$target" 2>&1
    backup_config
    local tmp; tmp=$(mktemp)
    jq --arg n "$target" 'del(.users[] | select(.name == $n))' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
    apply_config && echo -e "${GREEN}Удалён.${NC}"
    press_enter
}

show_uri() {
    mapfile -t users < <(jq -r '.users[].name' "$CONFIG_FILE")
    [[ ${#users[@]} -eq 0 ]] && { echo "Пусто."; press_enter; return; }
    for i in "${!users[@]}"; do echo "  $((i+1))) ${users[$i]}"; done
    read -rp "Номер: " n
    [[ -z "$n" ]] && return
    local user="${users[$((n-1))]}"
    local pwd; pwd=$(jq -r --arg n "$user" '.users[] | select(.name == $n) | .password' "$CONFIG_FILE")
    print_uri_for "$user" "$pwd"
    press_enter
}

server_status() {
    mita_cli status 2>&1
    echo
    mita_cli get connections 2>&1
    echo
    mita_cli get metrics 2>&1 | head -30
    press_enter
}

main_menu() {
    check_root; check_deps
    while true; do
        clear
        echo "╔══════════════════════════════════════╗"
        echo "║   mieru User Manager                 ║"
        echo "║   IP: $SERVER_IP"
        echo "╚══════════════════════════════════════╝"
        echo
        echo "  1) Список пользователей"
        echo "  2) Добавить"
        echo "  3) Удалить"
        echo "  4) URI клиента"
        echo "  5) Статус сервера"
        echo "  0) Выход"
        read -rp "Выбор: " opt
        case "$opt" in
            1) list_users ;; 2) add_user ;;
            3) delete_user ;; 4) show_uri ;;
            5) server_status ;; 0) exit 0 ;;
        esac
    done
}

main_menu

См. также