Unknown Discovering — распознавание QUIC/HTTPS 0-RTT без SNI
Точка входа для LLM при анализе выгрузок неопознанного трафика без видимого SNI. Метод не завязан на QUIC: он применим к любому трафику, где в ClientHello нет SNI — QUIC 0-RTT (quic_unknown), HTTPS/TLS 1.3 с 0-RTT (early data), session resumption, ECH. Здесь описан метод классификации IP-узлов CDN по их роли в инфраструктуре провайдера сервисов. Наборы признаков (сеты хостов) по провайдерам — в пространстве имён :services.
Когда применять
- Задача — классифицировать трафик QUIC или HTTPS с 0-RTT, в котором отсутствует SNI (поле host пустое).
- На вход — выгрузки из внутренней системы QoE: агрегированный netflow (top hosts) и сырой netflow (raw flow).
- Цель — определить роль конкретного узла
IP:443в инфраструктуре CDN и присвоить метку сервиса (напримерyoutube,google_play).
Общие принципы анализа
- Работа поверх DPI: есть риск, что протокол уровня приложений распознан неверно. Особенно осторожно — с протоколами малого объёма: при недостатке данных ложные срабатывания чаще.
- В нераспознанный трафик часто попадают 0-RTT пакеты. Анализируй окружение неизвестного протокола, а не отдельный флоу.
- Оценивай вероятность наличия того или иного трафика на AS. Возможно, что под сервис маскируется другой тип трафика (в т.ч. через SNI). Если AS принадлежит крупной компании (например Meta) — ожидаемо увидеть трафик её сервисов.
- Проверяй однотипные выгрузки из нескольких временных диапазонов — это даёт стабильный набор признаков.
Алгоритм классификации IP → сервис
Метод строит «землю» (ground truth) из флоу, где SNI виден, и применяет её к тому же IP там, где SNI отсутствует.
Шаг 1. Кандидаты
Из агрегированного netflow получить top host IP by traffic за период, отфильтровав неопознанный трафик без SNI, сортировка по суммарному объёму в обе стороны. Фильтр зависит от того, что разбираем: для QUIC это application protocol = “quic_unknown …”, для HTTPS 0-RTT — соответствующий неопознанный HTTPS/TLS-протокол. Это IP-кандидаты на классификацию.
Шаг 2. Земля по SNI
По каждому IP-кандидату выгрузить сырой netflow, где поле host (SNI) не пустое. Это выборка: какие SNI реально жили на этом IP.
Схема raw flow (важно):
Host IP— канонический IP сервера, по нему группируем.Host— SNI/hostname, это и есть признак.- Серверный порт двунаправленного флоу выводится из направления: если
Host IP == Source IPv4, то серверный порт =Source port, иначе =Destination port. Порт 443 попадает то в источник, то в назначение — прямой фильтр поDestination port = 443отрежет половину трафика.
Шаг 3. Оценка попадания в сет
Для каждого IP посчитать долю записей, чей host попадает по маске в набор сервиса. Если доля превышает порог сервиса — присвоить метку узлу IP:443.
Правила матчинга
- Знаменатель — все записи с непустым host на данном IP.
- Доля — записи, чей host совпал с любой маской набора, к знаменателю (по числу записей, не по объёму и не по уникальным FQDN).
- Семантика
*— fnmatch: звёздочка = любая подстрока, включая точки.*storage.googleapis.comловит иstorage.googleapis.com, иfirebasestorage.googleapis.com, иbucket.storage.googleapis.com. - Регистр — матчинг регистронезависимый.
- Порог — у каждого сервиса свой (см. страницы в :services). Если порог берут несколько сервисов на одном IP — метка по максимальной доле.
- Доп. фильтры набора. Помимо масок host, набор может нести дополнительные фильтры (например по
Host AS— номеру автономной системы). Записи, не проходящие фильтр, не учитываются ни в числителе, ни в знаменателе набора. Фильтры смотри на странице провайдера; они могут задаваться индивидуально для каждого набора.
Выделение и дополнение сетов
Отдельная задача: не проверить IP против готового набора, а построить набор масок для нового сервиса или дополнить существующий. Это обратный ход алгоритма.
Что запросить
Сырой netflow для изучаемого Host IP, где поле Host не пустое (та же схема, что на шаге 2: Host IP, Host, Source/Destination IPv4 + порт). Инструмента автоматической выгрузки пока нет — запросить выгрузку у аналитика (ручной ввод xlsx/csv).
Процедура
- Сгруппировать по
Host IP; взять записи с непустымHost; серверный порт вывести как на шаге 2. - Построить распределение FQDN: число записей на каждый
Host(при необходимости — по eTLD+1). Отдельно посмотреть распределение по объёму (Octet delta) и отметить, если оно расходится с распределением по числу записей. - Определить доминирующее семейство сервиса на IP. Проверить, что это не сторонний контент на общей инфраструктуре (см. мультитенантность).
- Обобщить FQDN в маски: свернуть случайные лейблы в wildcard (
rr1—sn-xxx.googlevideo.com→*.googlevideo.com); предпочитать суффиксные маски / eTLD+1;*по fnmatch (подстрока с точками). Маски держать достаточно узкими, чтобы не пересекались с наборами других сервисов. - Выбрать порог: высокий (напр. 80%) для сервисов с высоким риском загрязнения общими фронтами (как youtube среди GFE), 50% для более «чистых» выделенных семейств. Обосновать выбор.
- Валидировать: прогнать маски по этой же выгрузке и, желательно, по выгрузкам из других временных диапазонов — набор стабилен, если те же IP/FQDN проходят порог в разных окнах.
- Зафиксировать на странице провайдера в :services в стандартном формате (метка, порог, маски) + заметка о причине порога и о рискованных масках.
Предостережения
- Не строить наборы по netblock / AS / IPv6-сигнатурам — это только подсказки для discovery, проверять по SNI-«земле».
- Одна выгрузка = одно временное окно; подтверждать набор по нескольким окнам до финализации.
- Следить за update/ad-инфраструктурой (
gvt1,2mdn,doubleclick), которая может завышать долю сервиса — решать таксономию явно.
Важные предостережения
- Мультитенантность. Принадлежность IP netblock'у провайдера ≠ сервис. Пример:
35.241.31.20— адрес Google Cloud (35.x), но по «земле» на 100% отдаётh3.market.xiaomi.com(магазин Xiaomi поверх GCP). Меткуgoogle_cloudставить нельзя — ни одна googleapis-маска не совпадает, и это правильный результат. Классифицируй по SNI-«земле», а не по владельцу адреса. - Общие фронты (GFE). На общих фронтах Google один IP отдаёт play + maps + cloud + прочее, и ни одна доля не дотягивает до порога — IP остаётся без метки. Это ожидаемо; не занижай порог, чтобы «дотянуть».
- Объём vs число записей. Доля считается по числу записей. Несколько тяжёлых видеосессий доминируют по объёму, но по числу host'ов картина обратная — не путай метрики.
- IPv6 и anycast. Среди кандидатов бывают IPv6 и anycast-адреса; «земля» протухает при ротации. Согласуй период шага 1 и шага 2.
Маршрутизация к наборам сервисов
Наборы масок по провайдерам — в :services:
DSL декларация
Формальная нотация для описания правил классификации. Основные конструкции: SELECT (выборка кандидатов, шаг 1), SET (декларация набора), RULE (правило классификации, шаг 3); плюс DISCOVER — обратный ход (выделение набора).
Общие соглашения:
- Маски host — семантика fnmatch:
*= любая подстрока, включая точки. - Доля совпадения считается только по числу записей (by records). Знаменатель — записи с непустым host на IP, прошедшие фильтры.
- Комментарии —
//до конца строки. - Строковые значения с пробелами/числами — в кавычках (напр.
“quic_unknown 49293”).
SELECT — выборка кандидатов (шаг 1)
Отбор IP-кандидатов из агрегированного netflow. Здесь же задаётся фильтр по AS — он применяется ко всей выборке.
CANDIDATES =
SELECT host_ip, total_volume
FROM top_ip_host_agg
WHERE application_protocol = "quic_unknown 49293"
AND host_as IN (28917) // Fiord
ORDER BY total_volume DESC
LIMIT 30
WHERE— условия отбора;host_as IN (…)— список AS (под несколько значений).- Фильтр по AS обычно живёт здесь, на шаге 1, и применяется ко всему пулу кандидатов. Пример: кеш-сервер Instagram развёрнут в Fiord (
AS28917), поэтому AS фильтруется на выборке, а не в наборе.
SET — декларация набора
Именованный набор: маски + порог. Фильтр — опционально.
SET instagram {
masks:
instagram.*.fbcdn.net
*.cdninstagram.com
threshold: 90%
}
masks— многострочный список масок (fnmatch).threshold— порог на набор; единственный источник истины по порогу.ttl— необязательный срок годности метки (напр.30d,60d,90d). По истечении узел требует повторной проверки, т.к. IP в CDN ротируются. Задаётся на набор.filter— необязательное поле. Обычно опускается, потому что фильтр по AS задаётся на шаге 1 (вSELECT). Указывается только когда конкретному набору нужен доп. фильтр, отличный от выборки шага 1 (см. наборы Google, гдеHost ASзадан пофакторно).
Пример с фильтром на уровне набора (наборы Google):
SET google_play {
masks:
play.googleapis.com
filter: host_as IN (15169) // основная AS Google
threshold: 50%
}
RULE — правило классификации (шаг 3)
Развёрнутая форма: ссылается на объявленный SET, порог берётся из набора.
RULE instagram {
for ip in RAW
match SET instagram
if match_share_by_records > instagram.threshold
then LABEL (ip:443) -> instagram
}
for ip in RAW— перебор IP из сырого netflow (записи с непустым host).match SET instagram— сверка host каждой записи с масками набора.match_share_by_records— доля по числу записей (единственная метрика в этом алгоритме).LABEL (ip:443) → instagram— присвоение метки узлу.- Если у набора задан
filter, он применяется здесь же: записи вне фильтра не идут ни в числитель, ни в знаменатель.
DISCOVER — выделение набора (обратный ход)
Не присваивает метку; возвращает распределение FQDN на IP, из которого строятся masks. Соответствует разделу «Выделение и дополнение сетов».
DISCOVER SET candidate FROM RAW where host_ip = "35.241.31.20" and host is not null GROUP BY host RANK BY records // по числу записей
Планируемое
- Автоматизированные инструменты для рутинных выгрузок (top hosts, raw flow по IP).
- Discovery новых сетов и источников такого типа трафика — пополнение страниц в :services.
Не актуально (нет инструмента для выгрузки)
- При выгрузке из QoE ограничивать размер через limit; запрещено превышать limit = 1000.
- При выгрузке RAW netflow не выставлять большие временные диапазоны (несколько минут), это сильно нагружает систему.