Последние пару лет я работаю в команде, которая занимается устойчивой к DPI (Deep Packet Inspection) туннельной инфраструктурой — по сути, альтернативой классическому VPN.
Когда начинаешь заниматься этим всерьёз, довольно быстро обнаруживается неприятная вещь: шифрование само по себе давно уже не означает, что трафик нельзя распознать. Содержимое пакетов действительно можно скрыть, но пакет от этого не исчезает. У него остаются размер, направление, время появления; соединение начинается с определённой последовательности сообщений, TLS‑клиент определённым образом представляется серверу, поток ускоряется, замедляется, замирает и снова начинает передавать данные.
Для человека всё это выглядит как куча зашифрованных байтов. Для классификатора — как вполне пригодный набор признаков.
Поэтому значительная часть нашей работы в итоге свелась не к тому, чтобы «зашифровать ещё сильнее», а к более практическому вопросу: что именно видит наблюдатель снаружи и по каким признакам он может решить, что перед ним не браузерный трафик, а туннель.
Об этом и расскажу.
Что именно палит DPI
Наивное представление о DPI выглядит примерно так: где‑то у провайдера стоит коробка, которая заглядывает внутрь пакета, узнаёт запрещённый протокол и блокирует его. Но для классификации совершенно не обязательно понимать содержимое трафика. Шифрование скрывает данные, но не уничтожает форму обмена данными, а у разных протоколов эта форма разная.
OpenVPN — классический пример. Его рукопожатие имеет узнаваемую сигнатуру, и для системы, специально настроенной на обнаружение такого трафика, это удобный признак. Не обязательно долго наблюдать за соединением: характерная последовательность появляется уже в самом начале.
С WireGuard история другая. У части его служебных сообщений — handshake initiation и handshake response — фиксированный размер. Содержимое зашифровано, но наблюдатель всё равно видит сам факт появления пакетов определённой структуры и размера.
Вообще, фраза «но ведь всё зашифровано» в таких задачах помогает гораздо меньше, чем кажется. Представьте непрозрачный грузовик: посмотреть, что у него внутри, нельзя. Но если каждый день в одно и то же время из одних ворот выезжают три одинаковых грузовика, затем один возвращается, а два уходят дальше, содержимое кузовов для понимания происходящего может оказаться не самым важным признаком.
С Shadowsocks и V2Ray‑семейством всё несколько интереснее. Иногда это объясняют совсем просто: DPI якобы видит высокую энтропию и понимает, что перед ним зашифрованный туннель. Проблема в том, что TLS‑шифртекст тоже высокоэнтропиен, поэтому одна лишь «случайность» данных ничего не доказывает.
Гораздо полезнее смотреть на соединение целиком. Если протокол пытается выглядеть как TLS, насколько его рукопожатие похоже на то, что производит настоящий браузер? Что происходит после рукопожатия? Каковы размеры пакетов, их последовательность, направления, паузы между ними? Именно здесь появляется пространство для статистической классификации.
Даже при полностью зашифрованном содержимом снаружи остаётся немало метаинформации: размеры первых пакетов, направление передачи, временные интервалы, характер начала соединения, структура TLS‑рукопожатия. DPI не обязательно должен «прочитать пакет и понять, что внутри». Ему достаточно найти устойчивый паттерн и научиться отличать его от обычного сетевого трафика.
Вот с этим мы и пытались работать.
Архитектура
Мы довольно рано решили не складывать все функции системы в один универсальный сервер и разнесли роли между несколькими типами узлов.
Есть клиент, control‑узлы и exit‑узлы. Отдельно существует арбитр, который формирует и подписывает список актуальных узлов. Клиент, получив такой список, сначала проверяет подпись и только после этого начинает ему доверять.
Само по себе это разделение, конечно, ничего не маскирует. Это архитектурная основа, поверх которой уже работают остальные механизмы.
Вообще, одна из вещей, к которым мы пришли в процессе разработки, — анти‑DPI довольно плохо укладывается в представление об одном «хитром протоколе». Нет одной волшебной настройки, после которой трафик вдруг становится правильным. Есть несколько разных наблюдаемых признаков, и с каждым приходится разбираться отдельно.
Что мы пытались замаскировать
TLS‑фингерпринт
Когда браузер устанавливает TLS‑соединение, он начинает с ClientHello. В нём достаточно много параметров, причём разные реализации TLS формируют это сообщение по‑разному. В результате сам способ, которым клиент начинает рукопожатие, становится классификационным признаком.
Мы используем uTLS и копируем ClientHello реальных браузеров — Chrome, Firefox, Edge и Safari. Варианты ротируются между соединениями. Это уменьшает полезность классификации, которая опирается именно на fingerprint рукопожатия.
Разумеется, ClientHello — лишь одна часть поведения соединения, и копирование браузерного рукопожатия ещё не превращает весь трафик в браузерный. Но оставлять стабильный и легко различимый TLS‑фингерпринт собственного клиента тоже особого смысла нет.
По той же причине служебные признаки туннеля не вынесены в отдельный хорошо заметный заголовок собственного протокола. Иначе конструкция получилась бы довольно забавная: сначала тщательно стараемся начать соединение как обычный TLS‑клиент, а сразу после этого отправляем собственное «здравствуйте, я туннель».
Транспорт поверх HTTP/2 через uTLS
Сам транспорт работает поверх HTTP/2 через uTLS. Снаружи наблюдатель видит TLS‑сессию с браузероподобным ClientHello, а полезная нагрузка передаётся уже внутри установленного TLS‑канала.
Здесь полезно не смешивать два уровня. Наблюдателю доступны само TLS‑рукопожатие, размеры зашифрованных данных, направление передачи и её временной рисунок. Содержимое транспорта находится внутри.
Поэтому одного браузероподобного ClientHello недостаточно. Если после него соединение ведёт себя совершенно не так, как ожидается от обычной сетевой активности, у классификатора всё равно остаётся достаточно материала. Из этого, собственно, выросли следующие части схемы.
Decoy‑трафик
Параллельно с основным потоком клиент делает настоящие запросы к реальным CDN.
Здесь нет идеи «спрятать один пакет среди других»: отдельные соединения остаются отдельными соединениями. Речь идёт об агрегированной сетевой активности клиента. Если классификация учитывает не только один flow, но и совокупность наблюдаемого поведения, реальный фоновый трафик делает эту картину менее однозначной.
Бесплатным это, естественно, не бывает. Дополнительные запросы означают настоящие соединения, TLS, шифрование и обработку данных, поэтому decoy‑трафик заметно увеличивает нагрузку на CPU клиента. Этот компромисс мы приняли сознательно.
Вообще, после некоторого времени работы над такими системами начинаешь меньше любить слова «лучше» и «хуже». Обычно полезнее сформулировать две вещи: какой признак мы пытаемся убрать и сколько нам это стоит. В данном случае цена — дополнительная вычислительная нагрузка.
Pacing
Ещё один доступный наблюдателю признак — форма потока во времени.
Сетевой трафик редко идёт идеальной ровной линией: появляются всплески, паузы, меняется направление передачи. У туннеля тоже складывается собственный временной рисунок, и если оставить его как есть, он превращается ещё в один признак для классификатора.
Поэтому мы используем pacing — сглаживание всплесков передачи во времени. Никакой отдельной криптографии здесь нет; это просто попытка уменьшить ещё один наблюдаемый признак на транспортном уровне.
Что происходит с незнакомым запросом
Есть и совсем приземлённая часть.
Если на сервер приходит запрос, который он не распознаёт как корректный, сервер не должен отвечать чем‑нибудь в духе: «Да, вы попали куда нужно, но неправильно представились».
Для неаутентифицированного клиента такой запрос заканчивается обычным 404, то есть само обращение к адресу не подтверждает назначение сервера.
Это имеет значение при active probing, когда наблюдатель не ограничивается анализом проходящего трафика, а сам начинает обращаться к найденным эндпоинтам. В нашем случае незнакомый запрос получает максимально скучный ответ: такого ресурса здесь нет.
Во что это выливается на практике
Здесь было бы удобно показать красивый набор графиков и назвать всё это бенчмарком эффективности, но такого бенчмарка у нас нет. Мы не тестируем систему на лабораторном стенде против конкретных моделей и версий DPI‑оборудования.
У нас есть полевые наблюдения в реальных сетях нескольких крупных российских операторов: Ростелеком, Билайн, Мегафон, МТС, Теле2 и Дом.ру. Именно так к этим данным и стоит относиться: разные реальные подключения и маршруты, а не эксперимент, где можно менять один параметр за другим и аккуратно измерять влияние каждого.
Цена всей описанной выше обвязки при этом вполне реальна. По нашим наблюдениям, она добавляет примерно 40–80 мс задержки относительно прямого соединения. Decoy‑трафик заметно загружает CPU клиента — лишнее шифрование и обработка данных никуда не деваются.
Со скоростью получилось интереснее. На части маршрутов средняя скорость не уступала прямому подключению к тому же ресурсу; вероятно, здесь сказывается распределение нагрузки между несколькими exit‑каналами. Но воспринимать это как способ «ускорить Интернет» я бы точно не стал: скорее это хороший пример того, насколько итог зависит от конкретного маршрута.
Основная задача всей конструкции гораздо скромнее: уменьшить число простых и стабильных признаков, по которым зашифрованный поток удобно классифицировать как отдельный туннельный протокол. Сам трафик никуда не исчезает, и сеть, разумеется, продолжает его видеть. Мы лишь стараемся сделать наблюдаемую картину менее удобной для простой классификации.
Открытая часть
Часть проекта открыта: SDK опубликован на GitHub, и код протокола обфускации и decoy‑логики можно посмотреть непосредственно в реализации, а не только в пересказе статьи.
Мне вообще нравятся технические тексты, после которых можно открыть код и проверить, насколько написанное совпадает с тем, что на самом деле уходит в сокет. В сетевых протоколах это особенно полезно: между красивой схемой и работающей реализацией иногда помещается немало интересного.
Если есть вопросы по архитектуре — задавайте в комментариях. На то, что знаю и что могу обсуждать публично, отвечу.
ссылка на оригинал статьи https://habr.com/ru/articles/1068156/