Skip to content

Repository files navigation

luci-app-trusttunnel

English version

Клиент TrustTunnel для OpenWrt 25.12+ с выборочным обходом блокировок по спискам доменов, настройкой и диагностикой в веб-интерфейсе LuCI.

TrustTunnel — открытый VPN-протокол, изначально разработанный AdGuard VPN. Его трафик неотличим от обычного HTTPS, что позволяет проходить сквозь DPI. Этот пакет поднимает клиент на роутере и решает главный вопрос домашнего использования: что именно гнать через туннель, а что оставить напрямую.


Содержание


Что делает пакет

  1. Поднимает клиент TrustTunnel как системную службу. Клиент запускается под procd с автоперезапуском, ждёт готовности сети и синхронизации часов, логирует в системный журнал.

  2. Интегрирует списки обхода сообщества. Каталог itdoginfo/allow-domains подтягивается прямо из GitHub, файлы выбираются галочками: региональные списки (Россия, Украина), категории (anime, block, geoblock, hodca, news, porn), отдельные сервисы (YouTube, Telegram, Discord, Meta, TikTok и другие) и CIDR-подсети по нескольким сервисам. Новые файлы в репозитории появятся в интерфейсе сами, без обновления пакета.

  3. Принимает свои домены. Два независимых списка: «обходить» — добавляется к выбранным спискам сообщества, и «не обходить» — всегда идёт напрямую и имеет приоритет над всем остальным.

  4. Выносит настройки в интерфейс. Сервер заводится импортом конфига, который выдал сервер, или заполнением полей вручную. Никакого редактирования TOML.

  5. Даёт диагностику. Пинг каждого адреса сервера с цифрами, сравнение внешнего IP через туннель и напрямую, и проверка конкретного домена: вводите youtube.com — получаете ответ, в каком списке он нашёлся, в какие адреса резолвится, попал ли в nftables-набор и пойдёт ли через туннель.


Установка

Одной командой на роутере:

sh -c "$(wget -O - https://raw.githubusercontent.com/NooBiToo/TrustTunnelOpenWrt/main/install.sh)"

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

  1. Проверяет, что это OpenWrt версии 25.12 или новее и что доступен apk.
  2. Ставит зависимости: kmod-tun, ip-full, curl, ca-bundle, ucode-mod-math. Ни один из них не входит в типовую прошивку: без kmod-tun не существует даже /dev/net/tun, а у busybox-варианта ip вовсе нет подкоманды tuntap, которой поднимается туннельное устройство.
  3. Проверяет dnsmasq на поддержку nftset и, если её нет, спрашивает подтверждение на установку dnsmasq-full.
  4. Скачивает luci-app-trusttunnel-*.apk из последнего релиза и ставит его.
  5. Определяет архитектуру и ставит бинарник клиента официальным скриптом TrustTunnel в /opt/trusttunnel_client.
  6. Перезапускает rpcd, чтобы LuCI увидел новый бэкенд.

Скрипт идемпотентен: повторный запуск обновляет пакет и бинарник, не трогая настройки, и корректно останавливает службу перед подменой бинарника.

Служба после установки выключена. Это сделано специально: сначала настройка, потом запуск.

Про dnsmasq и dnsmasq-full

Проверено на живом OpenWrt 25.12.5: штатный dnsmasq собран без поддержки nftsetdnsmasq --test на строке с nftset= отвечает recompile with HAVE_NFTSET defined. Без dnsmasq-full режим «обход по списку» (режим по умолчанию) не работает вовсе: dnsmasq не может класть адреса доменов в nftables-набор, и набор обхода остаётся пустым, что бы вы ни выбрали в списках. Поэтому установщик по умолчанию предлагает поставить dnsmasq-full.

Установка dnsmasq-full заменяет системный пакет dnsmasq и перезапускает DNS на роутере — на несколько секунд DNS-запросы клиентов LAN не будут обслуживаться. Поэтому замена происходит только после вашего подтверждения. Если отказаться, продолжит работать только режим «всё через VPN».

Какие роутеры поддерживаются

Сам пакет архитектурно-независим: внутри только скрипты, ucode, JS и конфиги, поэтому один и тот же .apk встаёт на любую платформу — он помечен noarch. Отдельных сборок под архитектуры не существует и не требуется.

Ограничение идёт от бинарника клиента, который пакет не содержит, а скачивает установщиком вендора. Готовые сборки TrustTunnel есть под:

CPU (uname -m) Типичные роутеры
aarch64 почти все современные потребительские модели: Xiaomi, Keenetic, GL.iNet, Asus среднего и верхнего сегмента
armv7l предыдущее поколение того же класса, часть Zyxel и Netgear
mipsel массовые недорогие модели на ath79 и ramips: TP-Link, D-Link, Netis
mips то же семейство с обратным порядком байт
x86_64 мини-ПК, виртуальные машины, самосборные шлюзы

Вместе это покрывает подавляющее большинство устройств, на которые вообще ставят OpenWrt.

Не поддерживаются: ARMv5 и ARMv6 (старые модели вроде некоторых arm926ej-s и xscale), mips64, riscv64, powerpc. На них бинарника клиента просто нет.

Проверка выполняется до установки чего бы то ни было и именно по uname -m — тому же признаку, по которому выбирает сборку установщик вендора. Раньше здесь сверялось имя пакетной архитектуры OpenWrt, и это давало ложный пропуск: цели arm_* включают ARMv5 и ARMv6, где uname -m отдаёт armv5tel или armv6l. Такой роутер проходил проверку, пакет ставился, и отказ приходил уже от вендора — оставляя пункт в меню и службу без клиента.


Настройка через интерфейс

Откройте Службы → TrustTunnel. Три страницы: «Состояние», «Настройки» и «Диагностика». Все настройки собраны на одной странице с вкладками, которые переключаются мгновенно, без перезагрузки.

Порядок настройки — сверху вниз по вкладкам страницы Настройки:

  1. Сервер — нажмите Импорт… и вставьте конфиг, который выдал ваш сервер TrustTunnel, либо заполните адрес, имя хоста, пользователя и пароль вручную.
  2. Списки — отметьте нужные списки сообщества и, если нужно, добавьте свои ссылки.
  3. Свои домены — при необходимости добавьте домены, которые нужно обходить или, наоборот, никогда не обходить.
  4. Общее — включите Запускать при загрузке, нажмите Сохранить и применить, затем Запустить на странице «Состояние».

Вкладка Сеть нужна редко: там MTU и внутренние параметры маршрутизации.

Настройки

Принимаются оба вида, которые выдаёт сервер: текст файла конфигурации и ссылка tt://. Они различаются автоматически, потому что setup_wizard принимает их разными, взаимоисключающими флагами — подсунутая как файл ссылка не разбирается вовсе.

Быстрый путь — Импорт конфига сервера: вставьте текст конфигурации, которую выдал ваш сервер TrustTunnel, и поля заполнятся сами. Разбором занимается штатная утилита setup_wizard из поставки клиента, поэтому формат поддерживается ровно тот, который умеет сам TrustTunnel. Импорт забирает все поля сервера: адреса, имя хоста, доступы, сертификат, custom_sni, client_random, DNS-серверы, транспорт и флаги anti-DPI и проверки сертификата.

Вручную нужно заполнить:

  • Имя хоста — используется для TLS-сессии, а не для маршрутизации.
  • Адресахост:порт или [ipv6]:порт. Можно указать несколько: клиент пингует их и сам выбирает самый быстрый.
  • Пользователь и Пароль.

Остальное имеет разумные значения по умолчанию. Менять стоит осознанно:

  • Транспорт — HTTP/2 или HTTP/3 (QUIC). QUIC часто быстрее, но в некоторых сетях UDP режут.
  • Обход DPI — включает противодействие глубокой инспекции пакетов.
  • Постквантовый обмен ключами — включён по умолчанию.
  • Не проверять сертификат — принимает любой сертификат. Только для самоподписанной установки, которой вы управляете.
  • Закреплённый сертификат (PEM) — вставьте сюда сертификат сервера, если он самоподписанный. Тогда системное хранилище доверия не нужно.

Списки

Галочки по файлам allow-domains, сгруппированные по разделам, с размером каждого файла. Каталог берётся из GitHub API и кэшируется на сутки; кнопка Обновить каталог сбрасывает кэш.

Ниже — поле Свои URL списков: любой адрес, отдающий по домену на строку. Скачивается вместе со списками сообщества.

Скачать списки сейчас выполняет загрузку и перегенерацию правил немедленно. В остальное время списки обновляет cron ежедневно в 04:17. Галочка Обновлять автоматически отключает только расписание — кнопка продолжает работать всегда.

Свои домены

Два поля.

Обходить — эти домены пойдут через туннель дополнительно к выбранным спискам. Поддомены сопоставляются автоматически: запись example.com покрывает и www.example.com — так работает сопоставление доменов в dnsmasq, и по той же причине запись для голого TLD (например, ua) покрывает всю зону целиком.

Не обходить — эти всегда идут напрямую и имеют приоритет над всем остальным. Работает даже когда домен есть в выбранном списке сообщества, и даже когда его IP-адрес общий с обходным доменом (типичная ситуация для CDN): исключение делает сам клиент по SNI, уже после того как ядро промаркировало пакет. Принимает домен, *.домен, IP-адрес или CIDR-диапазон.

Домены с не-ASCII символами отбрасываются на этапе нормализации — используйте punycode (xn--…). Число отброшенных записей видно в интерфейсе.

Состояние и проверка

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

Три инструмента диагностики:

  • Пинг сервера — по каждому адресу: потери и min/avg/max.
  • Сравнить внешний IP — показывает внешний адрес через туннель и напрямую рядом. Если они одинаковые, трафик через туннель не идёт.
  • Проверить домен — главный инструмент, когда «не работает». Вводите домен и получаете: нормализованное имя, в каких выбранных списках он найден, в какие адреса резолвится, попали ли они в набор обхода, и вердикт с объяснением причины. Учтите: проверка резолвит домен через роутерный dnsmasq, то есть попутно наполняет набор его адресами. Домен из списка, которого никто ещё не спрашивал, после проверки начнёт уходить в туннель — сама проверка этому и поспособствовала, а не обнаружила уже готовое состояние. Вердикт пойдёт в туннель после резолва означает ровно это.

Внизу — хвост журнала клиента.


Вкладка «Диагностика»

Отдельная вкладка, которая прогоняет всю цепочку за один раз и выдаёт список: что проверено, каков вердикт и — если что-то не так — что с этим делать. Проверка запускается сама при открытии страницы; кнопка «Проверить снова» нужна для повторного прогона после исправлений. Проверки идут в том порядке, в котором работает пакет, поэтому читать список нужно сверху вниз и искать первое место, где всё разошлось.

Группа Что проверяется
Настройки адрес сервера, доступы, имя хоста для TLS, режим, выбранные списки
Предпосылки бинарник клиента, поддержка nftset в dnsmasq, устройство /dev/net/tun
Служба включена в автозапуске, работает ли сейчас
Состояние ядра устройство и его MTU, носитель туннеля, правило и таблица маршрутизации, таблица nftables, зона фаервола, размер набора обхода
Списки число доменов, время обновления, опубликован ли конфиг для dnsmasq
Сеть доступность сервера, идёт ли трафик через туннель

Вердикты различают три состояния, и это различение важнее, чем кажется:

  • в порядке — проверено и работает;
  • проверьте — работает, но есть замечание; например туннель настроен, а носителя нет, потому что клиент ещё не установил соединение;
  • проблема — не работает, и рядом написано, что сделать;
  • пропущено — проверять нечего, потому что предыдущее звено не готово. Пропуск специально не показывается как отказ: при выключенной службе проверки ядра не должны краснеть.

Отдельно стоит понимать разницу между устройством и носителем туннеля. Устройство создаёт клиент, и если оно есть с нужным MTU, а маршрут к нему привязан — наша часть выполнена. Носитель появляется только когда клиент присоединился и туннель поднялся. Если устройство в порядке, а носителя нет, искать причину надо в журнале клиента, а не в маршрутизации.

Версии

Внизу страницы состояния — версия установленного пакета и версия клиента TrustTunnel, и у каждой своя проверка обновлений: пакет сверяется с релизами этого репозитория, клиент — с релизами TrustTunnelClient, потому что клиент выходит у вендора отдельно от пакета и новая версия одного не означает новой версии другого. Результат проверки кэшируется на шесть часов: у GitHub лимит 60 неавторизованных запросов в час на адрес, а страница опрашивается регулярно. Кнопка «Проверить сейчас» спрашивает GitHub немедленно, не дожидаясь конца кэша.

Проверка различает «обновлений нет» и «проверить не удалось» — второе показывается отдельной строкой. Если сеть недоступна, показывается последний сохранённый результат с пометкой. Отдельно назван и третий случай: установленная версия новее последнего релиза (сборка из main либо кэш, который не удалось обновить) — «актуальной версией» это не считается.

Обновиться — запустить install.sh снова: он останавливает службу, заменяет пакет, ставит клиента и поднимает службу обратно, если она работала. Настройки при этом сохраняются, потому что /etc/config/trusttunnel объявлен в conffiles.


Два режима работы

Режим переключается на вкладке «Настройки» и меняет принцип отбора трафика.

Обход по списку (selective) — по умолчанию

dnsmasq на роутере, отвечая на DNS-запрос по домену из выбранных списков, записывает полученные адреса в набор nftables. Правило в ядре помечает пакеты к этим адресам, и только они уходят в туннель. Остальной трафик остаётся в быстром пути ядра и идёт с полной скоростью канала.

Выбирайте этот режим, если нужен обход блокировок без потери скорости на остальном трафике. Это основной сценарий, и он требует dnsmasq-full.

Всё через VPN (full)

Помечается весь форвардный трафик из LAN. Списки в этом режиме не используются: исключения делает сам клиент по SNI на основе списка «не обходить» (плюс, если включена галочка Отправлять выбранные списки напрямую, домены выбранных списков сообщества передаются клиенту как исключения).

Выбирайте, если нужно скрыть весь трафик, а не обойти отдельные сайты.

Сравнение

Обход по списку Всё через VPN
Скорость необойдённого трафика полная, ядерный fast-path ограничена userspace-обработкой
Выбранные списки определяют, что идёт в туннель не используются (кроме full_exclude_lists)
«Не обходить» работает работает
Нужен dnsmasq с nftset да нет
Зависит от того, чей DNS у клиентов LAN да нет

Трафик самого роутера по умолчанию идёт напрямую в обоих режимах. Это сделано намеренно: иначе клиент замкнулся бы на себя, а при сломанном туннеле роутер стало бы нечем починить. Включается галочкой Маршрутизировать также трафик самого роутера (include_router_traffic) отдельно.


Как это работает внутри

Отбор трафика

Tun-устройство создаёт КЛИЕНТ, а не пакет, и имя выбирает сам (обычно tun0). Задать его нельзя: в схеме конфигурации клиента такого параметра не существует. Первоначально пакет создавал устройство сам и рассчитывал, что клиент к нему присоединится, — это оказалось неверным допущением: клиент молча создавал своё, а созданное нами оставалось без носителя, и помеченный трафик уходил в мёртвый интерфейс.

Поэтому маршрут по умолчанию привязывается к устройству клиента после того, как оно появилось. Основной механизм — hotplug-скрипт: он срабатывает ровно тогда, когда устройство создано, и покрывает как первый запуск, так и пересоздание устройства после перезапуска клиента силами procd.

Дальше всё держится на трёх вещах:

  • таблица маршрутизации 880 с маршрутом по умолчанию через устройство клиента;
  • правило ip rule fwmark 0x9527 lookup 880;
  • своя nft-таблица inet trusttunnel, которая ставит эту метку.

Своя таблица, а не врезка в fw4, выбрана намеренно: её не смывает fw4 reload, и она не привязывает пакет к внутренностям firewall4. Ruleset для обоих режимов проверен реальным nft -c -f - на живом роутере.

Цепочка всегда пропускает без метки: трафик не из LAN, трафик к адресам самого сервера (иначе соединение клиента ушло бы в туннель, который он же и устанавливает) и трафик к приватным адресам.

Поток данных в режиме «обход по списку»:

allow-domains (выбранные файлы) ─┐
свои домены «обходить»           ─┼─→ gen-lists ─→ /var/etc/trusttunnel/dnsmasq.conf
свои URL списков                 ─┘                nftset=/домен/4#inet#trusttunnel#tt_bypass4
                                                     nftset=/домен/6#inet#trusttunnel#tt_bypass6
                                                     server=/домен/<list_resolver>
Subnets/IPv4|IPv6/*.lst          ────→ gen-lists ─→ /var/etc/trusttunnel/elements.nft

UCI /etc/config/trusttunnel      ────→ gen-config ─→ /var/etc/trusttunnel/client.toml
свои домены «не обходить»        ────→                exclusions = [...]

В режиме «всё через VPN» gen-lists не вызывается вовсе: dnsmasq.conf и elements.nft не создаются. В client.toml попадают exclusions из списка «не обходить» и, если включён full_exclude_lists, домены выбранных списков.

Механизм проверен на живом роутере целиком: при подготовленном конфиге запрос обходного домена с клиента LAN приводил к тому, что dnsmasq сам добавлял резолвнутые адреса в nftables-набор.

Killswitch

В таблице 880 рядом с маршрутом через устройство клиента лежит blackhole с большой метрикой. Пока устройство живо, работает основной маршрут. Как только устройство исчезает, ядро само убирает его маршрут, и помеченный трафик попадает в blackhole — то есть отбрасывается, а не утекает к провайдеру. Встроенный killswitch клиента при этом выключен, чтобы двое не управляли фаерволом одновременно.

DNS

Для каждого домена из списков генерируется не только строка nftset=, но и server=/домен/<резолвер>. Кто именно резолвит и чем защищён запрос, определяет параметр list_dns с тремя значениями.

plain (по умолчанию) — обычный DNS на адрес из list_resolver. Адрес резолвера статически добавляется в набор обхода, поэтому запросы именно по обходным доменам уходят внутрь туннеля: провайдер их не видит и не может подменить ответ. Если бы мог — в набор попали бы подставные адреса, и обход перестал бы работать. Остальные запросы идут обычным путём, без лишней задержки.

doh — шифрованный DNS. Служба поднимает на роутере локальный https-dns-proxy отдельным экземпляром procd и направляет dnsmasq на него. Запрос уходит шифрованным, но напрямую, а не через туннель — провайдер видит факт обращения к резолверу, но не содержимое. Адрес резолвера в набор обхода при этом не добавляется: шифрованному запросу туннель не нужен.

Этот режим нужен, когда важна фильтрация по своему профилю — например NextDNS: https://dns.nextdns.io/<ваш-id>. По умолчанию он обслуживает только домены из списков; как распространить его на всю сеть — ниже. Пакет https-dns-proxy в зависимостях не объявлен намеренно: шифрованный DNS нужен не всем, а лишние 400 КБ на роутере с 8 МБ флеша заметны. Если его нет, строки server= не выдаются вовсе — домены резолвит провайдер, обход продолжает работать, а диагностика говорит, что поставить.

Важно после apk add https-dns-proxy. У пакета есть собственный init-сервис, и при установке он сам стартует и переписывает DNS всей сети на себя — ставит noresolv и направляет dnsmasq на свои экземпляры 127.0.0.1#5053/5054. Это рабочая конфигурация, и оставить её как есть правильно: сеть получает шифрованный DNS целиком, а наш экземпляр на 5460 продолжает обслуживать домены из списков. Проверить стоит одно — отвечают ли его резолверы:

nslookup -po=5053 example.com 127.0.0.1
nslookup -po=5054 example.com 127.0.0.1

Если один из них молчит, перестаёт открываться всё, кроме доменов из списков: с noresolv других upstream у dnsmasq нет. Лечится это правкой его конфига — смените resolver_url неотвечающего экземпляра или удалите сам экземпляр:

uci delete https-dns-proxy.@https-dns-proxy[1]
uci commit https-dns-proxy && /etc/init.d/https-dns-proxy restart

Выключать сервис целиком не надо. Прежде здесь стоял именно такой совет — stop, disable и чистка его записей из конфига dhcp, — и он оставлял сеть в худшем положении, чем находил. Вместе с записями уходит noresolv, dnsmasq откатывается на DNS провайдера из PPPoE, а тот подменяет ответы: проверено на живом роутере, instagram.com приходил как 188.186.154.88 вместо адреса Meta. Картина «работают только сайты из списков» при этом никуда не уходит — наш экземпляр отвечает ровно за списочные домены, и всё остальное достаётся провайдеру.

Вкладка «Диагностика» проверяет это сама: она спрашивает каждый прописанный экземпляр и жалуется только на тот, который молчит.

Если включена галочка «применять этот резолвер ко всей сети», resolver_url этих экземпляров принадлежит нашей службе: правка руками переживёт только до следующего её старта. Менять адрес в этом режиме надо в настройках пакета.

Резолвер для всей сети

По умолчанию заданный здесь резолвер обслуживает только домены из списков: для каждого выдаётся строка server=/домен/127.0.0.1#5460, и больше ничего. Весь остальной DNS сети идёт туда, куда его направил штатный https-dns-proxy — обычно в Cloudflare. Отсюда самая частая претензия к этой настройке: указали NextDNS, а проверялка показывает Cloudflare. Проверялка права: test.nextdns.io в списках обхода не лежит.

Галочка «Применять этот резолвер ко всей сети» (doh_network) закрывает разрыв. Пока служба работает, она переводит на ваш URL штатный https-dns-proxy — все его экземпляры сразу, — а при остановке возвращает его настройки. Конфиг dnsmasq при этом не трогаем вовсе: noresolv и server=127.0.0.1#5053 прописывает его собственный init, у которого для этого есть готовый механизм с бэкапом и откатом.

Переводятся именно все экземпляры, а не первый: их init не знает опции disabled и поднимает каждую секцию, а dnsmasq держит все бездоменные server= в одном пуле и раскидывает запросы по ним. Один забытый экземпляр тихо отдавал бы часть запросов чужому резолверу.

Своим снипетом в conf-dir общий DNS забрать нельзя, и это проверено на живом роутере отдельным экземпляром dnsmasq: wildcard-домен /#/ приоритета над бездоменными server= не имеет и попадает с ними в тот же пул. При server=127.0.0.1#5053 рядом с server=/#/127.0.0.1#5460 отвечал Cloudflare. То есть наш резолвер обслуживал бы «иногда» — хуже честного «никогда».

Что стоит знать до включения:

  • при старте и остановке службы перезапускается dnsmasq, поэтому сеть на долю секунды остаётся без DNS;
  • если резолвер перестанет отвечать, без DNS останется вся сеть, а не только домены из списков: у dnsmasq стоит noresolv, других upstream нет;
  • свой экземпляр на list_doh_port в этом режиме не поднимается, и строки server= для доменов из списков не выдаются — они указывали бы на тот же резолвер, что и общий upstream;
  • если вы правили resolver_url руками, пока галочка стояла, служба это заметит при остановке и НЕ станет затирать правку своим бэкапом — но и вернуть прежние значения тогда будет нечем;
  • по умолчанию галочка снята, и обновление пакета её не включает.

provider — своего резолвера не задаём, домены резолвит то, что уже настроено на роутере.

Почему для plain невозможен DoT или DoH напрямую: значение уходит в директиву dnsmasq server=, а она принимает только обычный адрес. Именно поэтому шифрование сделано через локальный прокси, а не настройкой поля.

Как адреса попадают в набор

Ядро отбирает трафик по адресу, а адреса в набор кладёт dnsmasq — отвечая на запрос по домену из списка. Причём только на запрос, дошедший до upstream: ответ, выданный из собственного кэша dnsmasq, в набор не попадает. Отсюда два следствия, которые стоит знать.

Первое: применение настроек пересоздаёт таблицу nftables с нуля, поэтому накопленные адреса каждый раз пропадают. Служба вместе с этим сбрасывает кэш dnsmasq сигналом HUP — иначе следующий запрос клиента получил бы ответ из кэша, набор остался бы пустым, и обход молча не работал бы до истечения TTL.

Второе: пока домен никто не спросил через роутер, его адресов в наборе нет. Для доменов списков сообщества это нормально — их больше тысячи, набор наполняется по ходу дела. Свои домены служба резолвит сама сразу после применения настроек: человек только что ввёл домен руками и ждёт, что тот заработает, а его устройство обычно уже держит адрес в собственном кэше DNS и роутер не спрашивает вовсе.

Устройство со своим DoH- или DoT-резолвером не проходит через dnsmasq роутера, поэтому его запросы никогда не попадают в этот механизм и сопоставление по спискам для него не работает — если только не включена галочка Перехватывать DNS клиентов (intercept_dns), которая перенаправляет UDP/TCP 53 на роутер и блокирует 853.

Что происходит при «Сохранить и применить»

Клиент читает свой конфиг один раз, при запуске, поэтому раньше любая правка настроек означала перезапуск: туннель рвался на несколько секунд. Хуже, что вместе с клиентом на это время снималась и nft-таблица — маркировать трафик становилось нечем, и помеченный трафик LAN уходил напрямую. То есть обычное сохранение настроек само открывало утечку, от которой пакет и защищает.

Теперь служба сравнивает применённое состояние (settings.tsv) с тем, что выгружается из UCI, и делает ровно то, что нужно изменившимся ключам:

Что изменилось Что происходит Туннель
Расписание автообновления списков ничего: его читает cron во время работы не рвётся
Выбранные списки, свои домены, адрес открытого резолвера, LAN-интерфейсы, killswitch, перехват DNS правила пересобираются и перезаливаются в ядро, маршрут перепривязывается к живому устройству не рвётся
Адрес и данные сервера, сертификат, MTU, режим работы, уровень журнала, тип резолвера для списков клиент перезапускается, маршрутизация при этом сохраняется — трафик держит killswitch, а не утекает напрямую рвётся
Номер таблицы, метка, включение и выключение службы полный перезапуск с разбором маршрутизации: прежнюю таблицу и прежнее правило иначе нечем снять рвётся

Разбор устроен как список разрешённых ключей: любой ключ, которого в нём нет, получает полный перезапуск. Лишний перезапуск стоит нескольких секунд, а пропущенное применение выглядело бы как «интерфейс показывает новое значение, а работает старое» — такой отказ нечем диагностировать. Полнота списка против схемы настроек проверяется тестом, поэтому новый параметр нельзя добавить, забыв решить, как он применяется.

В полном режиме (full) правка списков тоже перезапускает клиента: при включённом «Список исключений из выбранных списков» они попадают в конфиг клиента, а не в правила dnsmasq.

Что где лежит

Путь Содержимое
/etc/config/trusttunnel Ваши настройки. Переживает обновление пакета
/usr/share/trusttunnel/lists/ Скачанные списки. Переживают перезагрузку, не попадают в бэкап конфигурации
/var/etc/trusttunnel/client.toml Сгенерированный конфиг клиента, права 600
/var/etc/trusttunnel/settings.tsv Настройки в плоском виде для генераторов, права 600
/var/etc/trusttunnel/endpoint.pem Закреплённый сертификат, если задан, права 600
/var/etc/trusttunnel/dnsmasq.conf Сгенерированные правила для dnsmasq
/var/etc/trusttunnel/elements.nft Статические элементы наборов nftables
/var/etc/trusttunnel/own.domains Свои домены после нормализации — их служба резолвит сама, чтобы наполнить набор, не дожидаясь запроса клиента
/tmp/dnsmasq.<секция>.d/trusttunnel.conf Копия (не ссылка) правил, которую читает dnsmasq; путь зависит от имени секции dnsmasq в UCI
/opt/trusttunnel_client/ Бинарники trusttunnel_client и setup_wizard

Файл в conf-dir dnsmasq — обычная копия, а не символическая ссылка. Это принципиально: сам dnsmasq на OpenWrt работает внутри ujail, куда /var/etc/trusttunnel не смонтирован, и по ссылке пройти не может. Проверено на живом роутере: с симлинком в журнале — cannot access …/trusttunnel.conf: No such file or directory, FAILED to start up, и procd уводит dnsmasq в crash loop, то есть установка убивает DNS всей сети. С обычным файлом-копией dnsmasq поднимается штатно.


Настройка без интерфейса

Ядро работает независимо от LuCI, поэтому настроить его можно и через UCI.

# Сервер
uci set trusttunnel.endpoint.hostname='vpn.example.com'
uci add_list trusttunnel.endpoint.address='203.0.113.10:443'
uci set trusttunnel.endpoint.username='alice'
uci set trusttunnel.endpoint.password='секрет'

# Режим
uci set trusttunnel.main.mode='selective'

# Списки
uci add_list trusttunnel.lists.source='Russia/inside-raw.lst'
uci add_list trusttunnel.lists.source='Services/youtube.lst'
uci add_list trusttunnel.lists.subnet='Subnets/IPv4/telegram.lst'

# Свои домены
uci add_list trusttunnel.domains.bypass='example.com'
uci add_list trusttunnel.domains.direct='bank.example'

# Включить и запустить
uci set trusttunnel.main.enabled='1'
uci commit trusttunnel
/etc/init.d/trusttunnel enable
/etc/init.d/trusttunnel start

Скачать списки и применить правила без перезапуска туннеля:

/etc/init.d/trusttunnel update_lists

Посмотреть, что получилось:

cat /var/etc/trusttunnel/lists.summary
/usr/libexec/trusttunnel/routing status /var/etc/trusttunnel/settings.tsv

Полный справочник настроек

Конфигурация в /etc/config/trusttunnel. Генерируемые из неё файлы клиента лежат в /var/etc/trusttunnel/ и правке не подлежат — они перезаписываются при каждом запуске.

Секция main

Опция По умолчанию Описание
enabled 0 Запускать службу при загрузке
mode selective selective — обход по списку, full — всё через VPN
full_exclude_lists 0 Только для full: отдать домены выбранных списков клиенту как исключения. Тысячи записей увеличивают расход памяти
log_level info info, debug, trace

Секция endpoint

Опция По умолчанию Описание
hostname Имя хоста для TLS-сессии, обязательно
address Список хост:порт, обязательно хотя бы один
username, password Авторизация, обязательны
protocol http2 http2 или http3
anti_dpi 0 Противодействие DPI
post_quantum 1 Постквантовый обмен ключами
skip_verification 0 Принимать любой сертификат
certificate Закреплённый сертификат в формате PEM
has_ipv6 1 Сервер умеет IPv6
dns_upstream Список DNS-серверов для запросов внутри туннеля. Пусто — AdGuard DNS без фильтрации
custom_sni Имя в TLS-рукопожатии вместо hostname, если сервер так настроен
client_random Префикс client random для защиты сервера от сканеров, hex[/маска]; без него такой сервер не пускает

Секция network

Опция По умолчанию Описание
mtu 1350 MTU на устройстве
early_ack 1 Читать SNI до того, как соединение установлено: рекомендация вендора при внешнем DNS и wildcard-исключениях. Клиент 1.1.5+
scannable_ports 443,80,8080,8008,853 Порты, на которых читается SNI; диапазон — 8080:8090
preresolve 1 Резолвить домены-исключения заранее, в фоне
preresolve_max 50 Сколько исключений резолвить за проход
tcp_recv_buf, tcp_send_buf 0 Буферы TCP-окна на соединение внутри туннеля, байт; 0 — 256 КБ клиента. Клиент 1.0.63+
table 880 Таблица маршрутизации
fwmark 0x9527 Метка фаервола
lan_devices Интерфейсы, чей форвардный трафик рассматривается. Пусто — взять устройство сети lan. Нужно задать вручную, если есть гостевая сеть
blackhole_on_down 1 Отбрасывать помеченный трафик при упавшем туннеле вместо утечки к провайдеру
include_router_traffic 0 Гнать в туннель и трафик самого роутера
list_dns plain Кто резолвит домены из списков: plain — обычный DNS через туннель, doh — шифрованный через локальный прокси, provider — то, что настроено на роутере. Только для selective
list_resolver 1.1.1.1 Адрес резолвера для режима plain. Принимает адрес#порт
list_doh_url пусто Адрес DoH-резолвера для режима doh, например https://dns.nextdns.io/<id>
list_doh_port 5460 Локальный порт, на котором слушает поднимаемый нами https-dns-proxy. Не используется при doh_network
doh_network 0 Сделать резолвер из list_doh_url DNS всей сети, а не только доменов из списков: служба переводит на него штатный https-dns-proxy, пока работает, и возвращает его настройки при остановке. Только для doh
intercept_dns 0 Перехватывать DNS клиентов: редирект порта 53 на роутер и блокировка 853. Только для selective

Секция lists

Опция По умолчанию Описание
auto_update 1 Обновлять списки по расписанию
update_interval daily Зарезервировано; расписание задаётся строкой cron
source Список файлов allow-domains, например Russia/inside-raw.lst
subnet Список файлов подсетей, например Subnets/IPv4/telegram.lst
url Список произвольных URL со своими списками

Секция domains

Опция Описание
bypass Домены, которые гнать через туннель дополнительно к спискам
direct Домены, IP или CIDR, которые всегда идут напрямую. Приоритет над всем остальным

Диагностика

Журнал

logread -e trusttunnel        # всё от пакета и клиента
logread -f -e trusttunnel     # следить в реальном времени

Журнал клиента идёт в logd, то есть в кольцевой буфер в памяти: без записи во флеш и без ротации.

Состояние ядра

# Что видит сам пакет
/usr/libexec/trusttunnel/routing status /var/etc/trusttunnel/settings.tsv

# Устройство, правило, таблица
ip link show "$(cat /var/etc/trusttunnel/device)"   # устройство клиента
ip rule show | grep 880
ip route show table 880

# Наборы: что реально попало под обход
nft list set inet trusttunnel tt_bypass4
nft list set inet trusttunnel tt_endpoint4

Проверить, идёт ли трафик через туннель

Проще всего — вкладка Состояние и проверкаСравнить внешний IP. Вручную:

curl --interface "$(cat /var/etc/trusttunnel/device)" https://api.ipify.org   # внешний IP через туннель
curl https://api.ipify.org                   # внешний IP напрямую

Разные адреса — туннель работает.

Частые проблемы

Служба не запускается, в журнале про CA-бандл. Не установлен ca-bundle. Поставьте его, либо закрепите сертификат сервера в настройках, либо отключите проверку сертификата.

В журнале жалобы на сертификат сразу после включения роутера. Часы не синхронизировались. Проверьте, что NTP работает.

Обход не работает для конкретного сайта. Проверьте домен инструментом Проверить домен. Частые причины: домена нет ни в одном выбранном списке; устройство пользуется своим DoH-резолвером и запрос не проходит через dnsmasq роутера; домен попал в список «не обходить».

Обход перестал работать после смены режима. В режиме «всё через VPN» списки не используются. Это ожидаемо.

Скорость упала на всём трафике. Вероятно, включён режим «всё через VPN». Для обхода блокировок нужен режим «обход по списку» — он не трогает остальной трафик.

Набор tt_bypass4 пустой. Значит dnsmasq ещё не отвечал на запросы по обходным доменам. Наборы наполняются по факту резолва, а не заранее. Обратитесь с клиента LAN к любому домену из списка и проверьте снова.


Ограничения

Это честный список того, чего пакет не умеет — и что с этим делать.

  • Устройство со своим DoH или DoT обходит сопоставление по спискам. Его DNS-запросы не проходят через dnsmasq роутера, поэтому адреса не попадают в набор обхода. Средство — галочка «перехватывать DNS клиентов» (intercept_dns), но она ломает сценарии, где свой резолвер нужен намеренно, поэтому выключена по умолчанию.
  • Запись списка вида example.com сопоставляется вместе со всеми её поддоменами. Так работает сопоставление доменов в dnsmasq. Из этого же следует, что запись для голого TLD (например, ua) покрывает всю зону целиком — при выборе таких списков это стоит иметь в виду.
  • Домены с не-ASCII символами отбрасываются. Используйте punycode (xn--…). Число отброшенных записей видно в интерфейсе.
  • Трафик самого роутера по умолчанию идёт напрямую. Включается отдельной галочкой «Маршрутизировать также трафик самого роутера» (include_router_traffic).
  • Домены списков сообщества попадают в набор по первому запросу, а не сразу. Адрес появляется в наборе, когда dnsmasq ответит на запрос по этому домену; своих доменов это не касается — их служба прогревает сама, а для тысячи с лишним доменов списков столько запросов при каждом применении это совсем другая цена. Устройство, у которого адрес уже лежит в собственном кэше DNS, роутер не спросит: если сайт продолжает идти напрямую, сбросьте кэш на устройстве (ipconfig /flushdns, перезапуск браузера).
  • Общий IP-адрес у CDN может протащить в туннель посторонний домен. Ядро отбирает трафик по адресу, а не по имени, поэтому один IP на несколько имён приводит к общей маркировке. Точное исключение — список «не обходить»: клиент сверяет его по SNI, уже после отбора по адресу, а не по IP.
  • Гостевые сети не подхватываются автоматически. Укажите их интерфейсы в lan_devices.
  • Поддерживается только OpenWrt 25.12 и новее. Пакет рассчитан на apk, пришедший на смену opkg; поддержки более старых версий не будет.
  • Несколько профилей серверов и автопереключение между ними не реализованы. Один сервер; несколько адресов одного сервера — можно.

Обновление

sh -c "$(wget -O - https://raw.githubusercontent.com/NooBiToo/TrustTunnelOpenWrt/main/install.sh)"

Повторный запуск установщика обновляет и пакет, и бинарник клиента. Настройки в /etc/config/trusttunnel не затрагиваются.


Удаление

# Остановить и выключить службу
/etc/init.d/trusttunnel stop
/etc/init.d/trusttunnel disable

# Удалить пакеты. Языковой пакет обязательно в том же вызове: он зависит от
# основного, и `apk del luci-app-trusttunnel` в одиночку молча ничего не
# сделает — сообщит "not removed due to" и выйдет с нулевым кодом.
apk del luci-i18n-trusttunnel-ru luci-app-trusttunnel

# Убрать бинарники клиента и скачанные списки
rm -rf /opt/trusttunnel_client /usr/share/trusttunnel

# Убрать задание cron
sed -i '/trusttunnel update_lists/d' /etc/crontabs/root
/etc/init.d/cron restart

Зона фаервола trusttunnel и правило переброса из lan остаются в /etc/config/firewall — удалите их вручную, если больше не нужны:

uci show firewall | grep trusttunnel   # найти секции зоны и forwarding
uci delete firewall.<секция_зоны>
uci delete firewall.<секция_forwarding>
uci commit firewall
/etc/init.d/firewall restart

Проверить, что ничего не осталось в ядре:

nft list table inet trusttunnel   # должно ответить, что таблицы нет
ip rule show | grep 880           # должно быть пусто
ip route show table 880           # маршрутов не должно быть

Настройки в /etc/config/trusttunnel остаются после удаления пакета — так переустановка не теряет конфигурацию. Удалите файл вручную, если это не нужно.


Поддержать проект

Пакет бесплатный. Если он вам помог, можно поддержать разработку криптовалютой — адреса ниже.

TON

UQD_JkHxRPrnkVPV560LT5zshYhe4ErkH-KALsKaNgPkJRmx

Tron (TRC-20, например USDT)

TJCjmqQu5p8g9DbVPY7FdC59LvtZCcjFVn

Ethereum / Polygon (EVM) — один адрес на обе сети:

0xd886FFA25b8816dDe1b7339D1ae2Ea4Ac9624b45

Solana

3XZGBNf2FuJ1ZKFrtWZXxLtfFKqxEVfW2JnmUuGtdqMx

Ключевые слова

OpenWrt, LuCI, TrustTunnel, VPN для роутера, VPN-клиент, обход блокировок, обход DPI, выборочный обход сайтов, split tunneling, selective routing, списки доменов, allow-domains, itdoginfo, podkop, dnsmasq, nftables, fwmark, killswitch, TUN, procd, apk, прошивка роутера, YouTube на роутере, Telegram, Discord, обход цензуры, установка одной командой


Лицензия и благодарности

Пакет распространяется по лицензии GPL-2.0. Полный текст — в файле LICENSE.

  • TrustTunnel — протокол, сервер и клиент, Apache 2.0.
  • itdoginfo/allow-domains — списки доменов сообщества.
  • itdoginfo/podkop — эталонная реализация обхода по спискам на OpenWrt, GPL-2.0. Подход к отбору трафика через dnsmasq и nftables сверялся с ней.

About

TrustTunnel VPN client for OpenWrt 25.12+ with LuCI web interface: selective domain-list bypass (allow-domains), DPI-resistant VPN, killswitch, diagnostics / Клиент TrustTunnel для OpenWrt: выборочный обход блокировок по спискам доменов, обход DPI

Topics

Resources

Stars

100 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages