Tnect для провайдеров: что приложение шлёт панели и что понимает в ответе

Tnect читает обычную ссылку подписки, как Happ или v2rayN: панели ничего не нужно менять. Здесь собрано, что приложение отправляет при обновлении, какие заголовки ответа оно понимает и какими путями достаёт подписку, когда адрес панели заблокирован.

Всё ниже относится к версии 0.26.0. Заголовки без учёта регистра.

Запрос

Обычный GET по адресу подписки. Без cookie, без повторов и без перехода по редиректам: ответ 3xx считается неудачей этого пути, и приложение пробует следующий.

ЗаголовокЗначение
user-agentTnect/0.26.0
x-hwid32 шестнадцатеричных символа. Для каждой подписки на телефоне свой, поэтому два провайдера не свяжут один телефон. Не меняется от обновления к обновлению и при обновлении приложения. Подписки, добавленные в старых версиях, могут прийти с прежним общим значением.
x-device-osandroid или ios
x-ver-osВерсия системы: на Android её номер, например 16, на iOS версия, как её даёт система.
x-device-modelМодель телефона латиницей, или mobile, если она неизвестна.

Ответ больше 5 МБ не принимается. На ответ главный адрес получает 9 секунд, или сколько попросит панель в subscription-request-timeout. Если ни один путь не ответил вовремя, приложение пробует ещё раз с запасом времени: так медленная сеть не выглядит как блокировка.

Ответ

Каждый заголовок можно передать и в самом теле подписки, строкой #имя: значение среди первых 30 строк, как это делает Happ. Если есть и то и другое, побеждает заголовок.

ЗаголовокЧто делает Tnect
profile-titleНазвание подписки на главном экране и в списке подписок. Можно в base64 с приставкой base64:.
subscription-userinfoupload=…; download=…; total=…; expire=…: байты и срок в секундах Unix. Под радаром видно, сколько израсходовано и сколько дней осталось. За сутки до expire и неделю после приложение спрашивает подписку каждый час, чтобы продление появилось само.
profile-update-intervalЧасы между обновлениями, когда в настройке «Обновлять подписку» выбрано «Авто» (так по умолчанию). Берётся от 1 до 12 часов; без заголовка 12.
support-urlКнопка «Поддержка» на экране подписок. Только https:// или tg://.
announceТекст, который человек видит, когда достигнут лимит устройств. Можно с приставкой base64:.
x-hwid-max-devices-reachedtrue или 1: лимит устройств исчерпан. Тело ответа не читается, серверы остаются прежними, показывается announce или «Достигнут лимит устройств». Следующая попытка через 3 часа или раньше, если интервал обновления короче.
x-hwid-not-supportedtrue или 1: «Подписка не принимает это устройство». Серверы остаются прежними.
provideridИмя провайдера. С ним приложение проверяет, что новый адрес из new-url или new-domain принадлежит тому же провайдеру. Подписанный маяк применяется к подписке, только если называет адрес её панели, а providerid, если он есть, должен ещё и совпасть: одного имени мало, его может написать любая панель. Принимается только из ответа по HTTPS.
subscription-request-timeoutСколько ждать ответа главного адреса, от 5 до 15 секунд.
new-urlНовый полный адрес подписки: https://, публичный адрес и путь. Приложение сразу пробует его и переезжает, только если там та же подписка: тот же providerid и то же название или те же серверы. Старый адрес 30 дней остаётся запасным.
new-domainНовый домен, можно с портом. Путь остаётся прежним; без порта сначала пробуется текущий порт, потом 443. Проверка та же, что у new-url.
fallback-urlРезервный адрес через фронтинг Google, на случай блокировки главного: https://www.google.com/<путь>#Имя?resolve-address=www.google.com&host=<сервис>.run.app. Из заголовка панели приложение принимает только связку www.google.com → *.run.app (Cloud Run). Хранится, пока не придёт другой верный.
profile-web-page-url
x-hwid-active
x-hwid-limit
Читаются, но пока ни на что не влияют.

Статус ответа

  • 2xx и хотя бы один сервер, который приложение может запустить: подписка обновлена.
  • Любой другой ответ, ошибка сети или HTML-страница вместо подписки: этот путь не сработал, приложение пробует следующий. HTML обычно значит, что ответил CDN или заглушка провайдера связи, а не панель.
  • 403 или 404 не HTML-страницей от fallback-url, если после него ничего не ответило: «Подписка не найдена или отключена».
  • Неудачное обновление никогда не убирает серверы: человек продолжает подключаться к тем, что были.

Подписка

Тело ответа одно из трёх:

  • список ссылок, по одной в строке;
  • такой же список в base64;
  • конфиг Xray в JSON: один объект или массив. Сервером становится первый подходящий outbound (vless, vmess, trojan, shadowsocks, socks, hysteria); старые списки vnext и servers приложение разворачивает само.

Ссылки

  • vless:// с TLS, REALITY или собственным шифрованием VLESS. Транспорты: raw (tcp), ws, grpc, httpupgrade, xhttp (splithttp), kcp.
  • vmess://: и в виде base64 от v2rayN, и обычной ссылкой.
  • trojan://, ss:// (методы AEAD и Shadowsocks 2022), socks://, hysteria2:// и hy2://.

Строку, которую ядро не запустит, приложение молча пропускает, остальные берёт. Имя сервера берётся из части после #; флаг-эмодзи в имени задаёт страну. Имена, sni и short id можно менять при каждом обновлении: сервер узнаётся по протоколу, адресу, порту и ключу, поэтому выбор человека, избранное и замеры не теряются.

Ссылки на приложение

tnect://add/<ссылка> и tnect://import/<ссылка> (ссылка как есть или в URL-кодировке), а также сами vless://, vmess://, trojan://, ss://, hysteria2:// и hy2:// открывают Tnect и вставляют ссылку в поле добавления. Добавляет её человек сам, кнопкой «Импортировать»: страница не может подписать его без нажатия.

Пути к панели

Приложение спрашивает подписку само: по интервалу, при открытии, если копии больше часа, когда серверы перестали отвечать (не чаще раза в 10 минут), у конца подписки, в новой сети и сразу после подключения. На Android ещё и в фоне, раз в шесть часов, даже с выключенным VPN. После неудачи следующая попытка через пару минут, потом реже, но не реже раза в два часа.

Пути пробуются по порядку, до первого ответа с подпиской:

  1. Через сервер включённого VPN, если это сервер той же подписки. Чужому провайдеру адрес вашей панели не показывается.
  2. Главный адрес. Затем объявленный новый (new-url, new-domain) и прежние адреса после переезда.
  3. Резервный адрес через фронтинг, fallback-url. Шесть часов после того, как он сработал, он идёт раньше главного адреса: главный, скорее всего, всё ещё заблокирован.
  4. Через сами серверы подписки: панель видит запрос с адреса вашего сервера.

Запросы мимо VPN идут по сети телефона с её DNS. Переехать на новый адрес или запомнить fallback-url и providerid можно только по ответу, пришедшему по HTTPS или через фронтинг.