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

Ключевое отличие от других протоколов: ВСЕ структурные поля сегмента (тип, размер, 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); лимит по числу одновременных сессий внутри одной учётки не задан в протоколе (можно опционально настраивать квоты по объёму трафика).

Такая модель радикально отличается от 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