Точка входа для LLM при анализе выгрузок неопознанного трафика без видимого SNI. Метод не завязан на QUIC: он применим к любому трафику, где в ClientHello нет SNI — QUIC 0-RTT (quic_unknown), HTTPS/TLS 1.3 с 0-RTT (early data), session resumption, ECH. Здесь описан метод классификации IP-узлов CDN по их роли в инфраструктуре провайдера сервисов. Наборы признаков (сеты хостов) по провайдерам — в пространстве имён :services.
IP:443 в инфраструктуре CDN и присвоить метку сервиса (например youtube, google_play).Метод строит «землю» (ground truth) из флоу, где SNI виден, и применяет её к тому же IP там, где SNI отсутствует.
Из агрегированного netflow получить top host IP by traffic за период, отфильтровав неопознанный трафик без SNI, сортировка по суммарному объёму в обе стороны. Фильтр зависит от того, что разбираем: для QUIC это application protocol = “quic_unknown …”, для HTTPS 0-RTT — соответствующий неопознанный HTTPS/TLS-протокол. Это IP-кандидаты на классификацию.
По каждому 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 отрежет половину трафика.
Для каждого IP посчитать долю записей, чей host попадает по маске в набор сервиса. Если доля превышает порог сервиса — присвоить метку узлу IP:443.
* — fnmatch: звёздочка = любая подстрока, включая точки. *storage.googleapis.com ловит и storage.googleapis.com, и firebasestorage.googleapis.com, и bucket.storage.googleapis.com.Host AS — номеру автономной системы). Записи, не проходящие фильтр, не учитываются ни в числителе, ни в знаменателе набора. Фильтры смотри на странице провайдера; они могут задаваться индивидуально для каждого набора.Отдельная задача: не проверить IP против готового набора, а построить набор масок для нового сервиса или дополнить существующий. Это обратный ход алгоритма.
Сырой netflow для изучаемого Host IP, где поле Host не пустое (та же схема, что на шаге 2: Host IP, Host, Source/Destination IPv4 + порт). Инструмента автоматической выгрузки пока нет — запросить выгрузку у аналитика (ручной ввод xlsx/csv).
Host IP; взять записи с непустым Host; серверный порт вывести как на шаге 2.Host (при необходимости — по eTLD+1). Отдельно посмотреть распределение по объёму (Octet delta) и отметить, если оно расходится с распределением по числу записей.rr1—sn-xxx.googlevideo.com → *.googlevideo.com); предпочитать суффиксные маски / eTLD+1; * по fnmatch (подстрока с точками). Маски держать достаточно узкими, чтобы не пересекались с наборами других сервисов.gvt1, 2mdn, doubleclick), которая может завышать долю сервиса — решать таксономию явно.35.241.31.20 — адрес Google Cloud (35.x), но по «земле» на 100% отдаёт h3.market.xiaomi.com (магазин Xiaomi поверх GCP). Метку google_cloud ставить нельзя — ни одна googleapis-маска не совпадает, и это правильный результат. Классифицируй по SNI-«земле», а не по владельцу адреса.Наборы масок по провайдерам — в :services:
Формальная нотация для описания правил классификации. Основные конструкции: SELECT (выборка кандидатов, шаг 1), SET (декларация набора), RULE (правило классификации, шаг 3); плюс DISCOVER — обратный ход (выделение набора).
Общие соглашения:
* = любая подстрока, включая точки.// до конца строки.“quic_unknown 49293”).Отбор 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 (под несколько значений).AS28917), поэтому AS фильтруется на выборке, а не в наборе.Именованный набор: маски + порог. Фильтр — опционально.
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%
}
Развёрнутая форма: ссылается на объявленный 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, он применяется здесь же: записи вне фильтра не идут ни в числитель, ни в знаменатель.
Не присваивает метку; возвращает распределение 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 // по числу записей