Перейти к содержимому

Архитектура

TailSocks обеспечивает стабильную и высокопроизводительную работу Tailscale на Android, запуская сетевой движок в пользовательском пространстве (userspace). На этой странице подробно описаны технические решения, применяемые для преодоления системных ограничений платформы Android.

Система разделена на три функциональных слоя:

  • Tailscale Демон (Ядро): Разделяемая библиотека формата Position Independent Executable (PIE) (libtailscale.so), скомпилированная из официальных исходников Tailscale с флагами сборки, исключающими немобильные компоненты (D-Bus, Kubernetes, AWS и т. д.). Тот же файл — и CLI: с апстримовским тегом ts_include_cli он работает как tailscale, когда его вызывают под этим именем (Консоль) или с TS_BE_CLI=1 (обёртка Root-режима).
  • Мост Управления (appctr): Контроллер на языке Go, взаимодействующий с демоном через локальные Unix-сокеты (tailscaled.sock) и предоставляющий удобный JNI-интерфейс для Android-слоя.
  • UI на Kotlin / Jetpack Compose: Слой представления, отвечающий за пользовательские настройки, визуализацию состояния и отображение диагностики в реальном времени.

Для поддержки нескольких профилей Tailscale на одном устройстве TailSocks реализует строгую изоляцию файловой системы и конфигураций:

  • Перенаправление Состояния (State Redirection): Каждому аккаунту присваивается уникальный ID, привязанный к изолированному каталогу состояния в files/states/{id}. Это гарантирует, что ключи устройств и кэши сетевых карт (netmap) разных аккаунтов никогда не пересекаются.
  • Кэш карты сети: tailscaled 1.104+ хранит последнюю карту сети в files/states/{id}/profile-data/<профиль>/netmap-cache/ и запускается по ней, не дожидаясь ответа сервера координации, — если сервер выдал узлу атрибут cache-network-maps. В «Настройки → Обход блокировок» его можно выключить (TS_USE_CACHED_NETMAP=false) и очистить; в резервные копии он не попадает.
  • Изоляция Настроек: Параметры приложения сохраняются в индивидуальных контейнерах SharedPreferences (appctr_{id}).
  • Жизненный Цикл Процесса: При смене аккаунта выполняется полная остановка демона и его повторная инициализация, что исключает утечку состояния между профилями.

Нативный движок DNS и реактивная синхронизация

Заголовок раздела «Нативный движок DNS и реактивная синхронизация»

Стандартные приложения Android не способны маршрутизировать UDP-пакеты в сеть userspace без использования системного TUN-интерфейса. TailSocks решает эту проблему с помощью реактивной многоступенчатой логики разрешения имен:

  1. Синхронизация Шины IPN (IPN Bus Synchronization): Go-мост поддерживает постоянное соединение со встроенной шиной уведомлений демона (mask=1032). Это позволяет отслеживать изменения NetMap, суффиксы MagicDNS и маршруты SplitDNSRoutes в режиме реального времени.
  2. Разрешение Имен Узлов в Памяти: Все узлы сети кэшируются в памяти по FQDN и коротким именам. Разрешение имен в доменной зоне *.ts.net происходит за 0 мс путем прямого обращения к кэшу в памяти.
  3. Split DNS (TCP-over-SOCKS5): Для доменов, соответствующих корпоративным маршрутам (например, company.internal), мост инкапсулирует UDP-запросы в TCP-фреймы и туннелирует их через SOCKS5 к внутреннему resolver IP, предоставленному в netmap.
  4. Умный Фолбэк Публичных Запросов (Smart Upstream Fallback): Публичные запросы (например, google.com) сначала отправляются через API dns-query в Tailscale. Если демон возвращает SERVFAIL (характерно для режима userspace), мост автоматически переключается на настраиваемые пользователем системные или DoH (DNS-over-HTTPS) резолверы.
  5. Кэш ответов: Каждый ответ шагов 3–4 хранится на время своего TTL (не меньше 10 с, не дольше 1 ч; NXDOMAIN и NODATA — 30 с или минимум из SOA, если он меньше). При попадании ответ получает ID транзакции клиента, а TTL записей уменьшаются на прошедшее время. Кэш сбрасывается вместе с кэшем узлов и при любом изменении маршрутов Split DNS, чтобы устаревший маршрут не отдавал старый ответ. Каждый запрос пишется в лог на уровне debug с полем cached=true|false.
  • Ограничения Android: Ограничения прав доступа (netlinkrib: permission denied) препятствуют тому, чтобы демон tailscaled самостоятельно опрашивал сетевые интерфейсы, что приводит к сбоям диагностики в ядре.
  • Решение: Мост appctr реализует нативный метод GetNetcheckFromAPI, запускающий пакет tailscale.com/net/netcheck непосредственно внутри процесса приложения.
  • Синхронизация Интерфейсов: С помощью переменной окружения TS_NET_STATE мост передает состояния сетевых интерфейсов (передаваемые из Kotlin) в ядро. Сетевой монитор NewStatic использует эти данные для формирования точных отчетов STUN/DERP в обход ограничений демона.

TailSocks включает надежный менеджер передачи файлов:

  • Входящие Файлы: Переменная окружения TS_TAILDROP_DIR указывает ядру на files/taildrop/<account> (core/TaildropPaths.kt) — каталог только с пользовательскими файлами, отдельно от ключей узла в files/states/.
  • DocumentsProvider: TailsocksFileProvider предоставляет для Android Storage Access Framework (SAF) именно эти каталоги Taildrop (и общие папки Taildrive по WebDAV). Это позволяет просматривать полученные через Taildrop файлы в системном приложении «Файлы».
  • Исходящие Файлы: Через SAF приложение читает файлы и передает их в file-put API демона через мост с корректным экранированием путей URL.

Самовосстановление и декларативная конфигурация (Stateless Configuration)

Заголовок раздела «Самовосстановление и декларативная конфигурация (Stateless Configuration)»

Для устранения проблем с «зависанием» маршрутов в userspace-движках:

  • Явное Управление Состоянием: Каждая операция обновления конфигурации включает явные сбрасывающие флаги (например, --exit-node=) для принудительной очистки старых параметров, не выбранных в UI. Для Serve/Funnel применяется паттерн «Reset-then-Apply» (отправка {} перед применением новой конфигурации).
  • Цикл Проверки Здоровья: Фоновая задача периодически проверяет наличие выбранного Exit Node в текущей сетевой карте аккаунта. Если узел становится недоступен или недействителен, конфигурация автоматически очищается.
  • Прокси Управляющего Сервера: Поддерживается работа через глобальные SOCKS5/HTTP-прокси для связи с сервером координации. Трафик SOCKS5 принудительно направляется через ALL_PROXY с очисткой HTTP_PROXY во избежание протокольных ошибок (например, “unknown Socks version”).

TailSocks поддерживает минимальный набор атомарных патчей для оригинального ядра Tailscale для обеспечения мобильной совместимости. Патчи располагаются в директории appctr/patches/ и внедряют возможности, недоступные через внешний LocalAPI:

  • 01-enable-socks-android: Включает поддержку SOCKS5 в режиме userspace-networking на Android.
  • 02-socks5-auth: Добавляет поля Username/Password в исходящий SOCKS5-слушатель (значения считываются из переменных окружения).
  • 03-taildrop-monolithic-fs: Регистрирует fsFileOps на чистом Go во избежание JNI-паник в Android и направляет Taildrop в изолированную директорию приложения. Ещё возвращает подсчёт байт при приёме файла (send.go), чтобы Notify.IncomingFiles показывал прогресс — начиная с 1.84 апстрим копирует мимо счётчика.
  • 04-vip-services: Добавляет виртуальные сервисы (VIP) в структуру HostInfo, делая их видимыми для сервера координации.
  • 05-localapi-cert: Включает компиляцию эндпоинта /cert для Android.
  • 06-android-netmon: Реализует кастомный netmon.InterfaceGetter для обхода ограничений netlink на Android 10+ и по умолчанию представляет узел серверу координации как Linux-клиент командной строки (OS = linux, App = tailscale-cli, DeviceModel = Tailsocks). При включённом «Сообщать настоящую ОС» (TS_HONEST_HOSTINFO=1) сообщает настоящее Android-устройство; в самом клиенте от этих полей ничего не зависит.
  • 07-taildrive-android: Адаптации Taildrive для платформы Android.
  • 08-netstack-cgnat: Исправление маршрутизации CGNAT в netstack.
  • 09-netstack-loopback: Loopback-маршрутизация пакетов, адресованных самому себе в netstack.
  • 10-taildrive-userspace-dial: Маршрутизация WebDAV удаленных пиров через tsdial.Dialer.
  • 11-noop-dns-fallback: Внедрение переменной окружения DNS fallback во избежание ошибок SERVFAIL.