From a8f2055c772bb8303a4b7f198b7192fa2a995714 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 9 Sep 2026 13:18:58 +0700 Subject: [PATCH] =?UTF-8?q?feat(netflow):=20=D1=80=D0=B0=D1=81=D1=88=D0=B8?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D1=8C=20=D0=BA=D0=B0=D1=82=D0=B0=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=20ASN=20=D0=BF=D0=BE=D0=BF=D1=83=D0=BB=D1=8F=D1=80=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В аналитике потока видны сети со своим AS (Apple, Steam, VK, Epic и другие), а не только Cloudflare/AWS. Holder и порты Steam не перебивают Cloudflare CIDR. Co-authored-by: Cursor --- .../src/services/traffic-flow-brands.test.ts | 34 +++ backend/src/services/traffic-flow-brands.ts | 255 ++++++++++++++---- .../services/traffic-flow-classify.test.ts | 65 +++++ backend/src/services/traffic-flow-classify.ts | 16 +- backend/src/services/traffic-flow-map-hops.ts | 7 +- 5 files changed, 316 insertions(+), 61 deletions(-) diff --git a/backend/src/services/traffic-flow-brands.test.ts b/backend/src/services/traffic-flow-brands.test.ts index b7f4d67..a365d6b 100644 --- a/backend/src/services/traffic-flow-brands.test.ts +++ b/backend/src/services/traffic-flow-brands.test.ts @@ -1,11 +1,14 @@ import assert from "node:assert/strict" import { brandByAsn, + brandByHolder, countryFromHolder, + isSteamGamePort, lookupBrand, OTHER_SERVICE, isNamedInternetService, mapServiceNodeId, + resolveFlowBrand, resolveRipeCountry, } from "./traffic-flow-brands.js" @@ -39,4 +42,35 @@ assert.equal(isNamedInternetService("DNS", "DNS"), false) assert.equal(mapServiceNodeId("AWS"), "svc:aws") assert.equal(mapServiceNodeId("Cloudflare"), "svc:cloudflare") +assert.equal(brandByAsn(714)?.service, "Apple") +assert.equal(brandByAsn(714)?.category, "CDN") +assert.equal(brandByAsn(36459)?.service, "GitHub") +assert.equal(brandByAsn(395701)?.service, "Epic") +assert.equal(brandByAsn(6507)?.service, "Riot") +assert.equal(brandByAsn(33353)?.service, "PlayStation") +assert.equal(brandByAsn(14061)?.service, "DigitalOcean") +assert.equal(brandByAsn(24940)?.service, "Hetzner") +assert.equal(brandByAsn(8403)?.service, "Spotify") +assert.equal(brandByAsn(13414)?.service, "X") +assert.equal(brandByAsn(47541)?.service, "VK") +assert.equal(brandByAsn(47764)?.service, "VK") +assert.equal(brandByAsn(30103)?.service, "Zoom") +assert.equal(brandByAsn(19281)?.service, "Quad9") +assert.equal(brandByAsn(9059)?.service, "AWS") +assert.equal(brandByAsn(396982)?.service, "Google") +assert.equal(brandByAsn(400645)?.service, "ChatGPT") + +assert.equal(brandByHolder("VALVE-CORPORATION")?.service, "Steam") +assert.equal(brandByHolder("OpenAI, LLC")?.service, "ChatGPT") +assert.equal(brandByHolder("YouTube LLC")?.service, "YouTube") +assert.equal(brandByHolder("AMAZON-AES - Amazon.com, Inc."), null) + +assert.equal(isSteamGamePort(17, 27015, 50000), true) +assert.equal(isSteamGamePort(6, 443, 50000), false) + +assert.equal(resolveFlowBrand("104.18.35.51", 32590, "VALVE-CORPORATION", 6, 443, 1)?.service, "Cloudflare") +assert.equal(resolveFlowBrand("203.0.113.9", 32590, "", 17, 27015, 50000)?.service, "Steam") +assert.equal(resolveRipeCountry("", 9059, ""), "IE") +assert.equal(resolveRipeCountry("", 24940, ""), "DE") + console.log("traffic-flow-brands.test.ts: ok") diff --git a/backend/src/services/traffic-flow-brands.ts b/backend/src/services/traffic-flow-brands.ts index 813cdb7..f24d24d 100644 --- a/backend/src/services/traffic-flow-brands.ts +++ b/backend/src/services/traffic-flow-brands.ts @@ -7,59 +7,162 @@ export interface BrandHit { category: string } +const CDN = { category: "CDN" } as const +const WEB = { category: "Веб" } as const +const VIDEO = { category: "Видео / стриминг" } as const +const GAMES = { category: "Игры" } as const +const VOICE = { category: "Голос" } as const +const AI = { category: "ИИ" } as const +const DNS = { category: "DNS" } as const + +const CLOUDFLARE: BrandHit = { service: "Cloudflare", ...CDN } +const FASTLY: BrandHit = { service: "Fastly", ...CDN } +const AKAMAI: BrandHit = { service: "Akamai", ...CDN } +const AWS: BrandHit = { service: "AWS", ...CDN } +const MICROSOFT: BrandHit = { service: "Microsoft", ...CDN } +const YANDEX: BrandHit = { service: "Yandex", ...CDN } +const APPLE: BrandHit = { service: "Apple", ...CDN } +const DIGITALOCEAN: BrandHit = { service: "DigitalOcean", ...CDN } +const HETZNER: BrandHit = { service: "Hetzner", ...CDN } +const OVH: BrandHit = { service: "OVH", ...CDN } +const ORACLE: BrandHit = { service: "Oracle", ...CDN } +const LINODE: BrandHit = { service: "Linode", ...CDN } +const VULTR: BrandHit = { service: "Vultr", ...CDN } +const SCALEWAY: BrandHit = { service: "Scaleway", ...CDN } +const IBM_CLOUD: BrandHit = { service: "IBM Cloud", ...CDN } +const ALIBABA: BrandHit = { service: "Alibaba", ...CDN } +const TENCENT: BrandHit = { service: "Tencent", ...CDN } +const GCORE: BrandHit = { service: "G-Core", ...CDN } +const CDN77: BrandHit = { service: "CDN77", ...CDN } +const SELECTEL: BrandHit = { service: "Selectel", ...CDN } +const TIMEWEB: BrandHit = { service: "Timeweb", ...CDN } +const BEGET: BrandHit = { service: "Beget", ...CDN } +const DDOS_GUARD: BrandHit = { service: "DDoS-Guard", ...CDN } +const META: BrandHit = { service: "Meta", ...CDN } + +const GOOGLE: BrandHit = { service: "Google", ...WEB } +const GITHUB: BrandHit = { service: "GitHub", ...WEB } +const GITLAB: BrandHit = { service: "GitLab", ...WEB } +const X: BrandHit = { service: "X", ...WEB } +const LINKEDIN: BrandHit = { service: "LinkedIn", ...WEB } +const VK: BrandHit = { service: "VK", ...WEB } +const REDDIT: BrandHit = { service: "Reddit", ...WEB } +const DROPBOX: BrandHit = { service: "Dropbox", ...WEB } +const SNAP: BrandHit = { service: "Snap", ...WEB } +const WIKIPEDIA: BrandHit = { service: "Wikipedia", ...WEB } +const PAYPAL: BrandHit = { service: "PayPal", ...WEB } +const SALESFORCE: BrandHit = { service: "Salesforce", ...WEB } + +const YOUTUBE: BrandHit = { service: "YouTube", ...VIDEO } +const NETFLIX: BrandHit = { service: "Netflix", ...VIDEO } +const TWITCH: BrandHit = { service: "Twitch", ...VIDEO } +const TIKTOK: BrandHit = { service: "TikTok", ...VIDEO } +const SPOTIFY: BrandHit = { service: "Spotify", ...VIDEO } + +const STEAM: BrandHit = { service: "Steam", ...GAMES } +const BLIZZARD: BrandHit = { service: "Blizzard", ...GAMES } +const EPIC: BrandHit = { service: "Epic", ...GAMES } +const RIOT: BrandHit = { service: "Riot", ...GAMES } +const PLAYSTATION: BrandHit = { service: "PlayStation", ...GAMES } +const ROBLOX: BrandHit = { service: "Roblox", ...GAMES } +const UBISOFT: BrandHit = { service: "Ubisoft", ...GAMES } + +const DISCORD: BrandHit = { service: "Discord", ...VOICE } +const TELEGRAM: BrandHit = { service: "Telegram", ...VOICE } +const ZOOM: BrandHit = { service: "Zoom", ...VOICE } + +const CHATGPT: BrandHit = { service: "ChatGPT", ...AI } + +const QUAD9: BrandHit = { service: "Quad9", ...DNS } +const OPENDNS: BrandHit = { service: "OpenDNS", ...DNS } + +function brandEntries(hit: BrandHit, asns: number[]): Array<[number, BrandHit]> { + return asns.map((asn) => [asn, hit]) +} + +function hqEntries(cc: string, asns: number[]): Array<[number, string]> { + return asns.map((asn) => [asn, cc]) +} + const ASN_BRANDS = new Map([ - [13335, { service: "Cloudflare", category: "CDN" }], - [209242, { service: "Cloudflare", category: "CDN" }], - [54113, { service: "Fastly", category: "CDN" }], - [20940, { service: "Akamai", category: "CDN" }], - [16509, { service: "AWS", category: "CDN" }], - [14618, { service: "AWS", category: "CDN" }], - [8075, { service: "Microsoft", category: "CDN" }], - [13238, { service: "Yandex", category: "CDN" }], - [32590, { service: "Steam", category: "Игры" }], - [57976, { service: "Blizzard", category: "Игры" }], - [2906, { service: "Netflix", category: "Видео / стриминг" }], - [40027, { service: "Netflix", category: "Видео / стриминг" }], - [15169, { service: "Google", category: "Веб" }], - [36040, { service: "YouTube", category: "Видео / стриминг" }], - [46489, { service: "Twitch", category: "Видео / стриминг" }], - [401115, { service: "ChatGPT", category: "ИИ" }], - [49544, { service: "Discord", category: "Голос" }], - [62041, { service: "Telegram", category: "Голос" }], - [59930, { service: "Telegram", category: "Голос" }], - [211157, { service: "Telegram", category: "Голос" }], - [32934, { service: "Meta", category: "CDN" }], - [396986, { service: "TikTok", category: "Видео / стриминг" }], + ...brandEntries(CLOUDFLARE, [13335, 209242]), + ...brandEntries(FASTLY, [54113]), + ...brandEntries(AKAMAI, [20940, 16625, 32787, 35994, 16702, 24319]), + ...brandEntries(AWS, [16509, 14618, 8987, 7224, 9059]), + ...brandEntries(MICROSOFT, [8075, 8068, 8069, 8070]), + ...brandEntries(YANDEX, [13238]), + ...brandEntries(APPLE, [714, 6185]), + ...brandEntries(DIGITALOCEAN, [14061]), + ...brandEntries(HETZNER, [24940, 213230]), + ...brandEntries(OVH, [16276]), + ...brandEntries(ORACLE, [31898]), + ...brandEntries(LINODE, [63949]), + ...brandEntries(VULTR, [20473]), + ...brandEntries(SCALEWAY, [12876]), + ...brandEntries(IBM_CLOUD, [36351]), + ...brandEntries(ALIBABA, [45102]), + ...brandEntries(TENCENT, [132203]), + ...brandEntries(GCORE, [199524]), + ...brandEntries(CDN77, [60068]), + ...brandEntries(SELECTEL, [50340, 49505]), + ...brandEntries(TIMEWEB, [9123]), + ...brandEntries(BEGET, [198610]), + ...brandEntries(DDOS_GUARD, [57724]), + ...brandEntries(META, [32934, 63293, 54115]), + ...brandEntries(GOOGLE, [15169, 396982]), + ...brandEntries(GITHUB, [36459]), + ...brandEntries(GITLAB, [54876]), + ...brandEntries(X, [13414]), + ...brandEntries(LINKEDIN, [14413, 40793]), + ...brandEntries(VK, [47541, 47764]), + ...brandEntries(REDDIT, [394706]), + ...brandEntries(DROPBOX, [19679]), + ...brandEntries(SNAP, [19750]), + ...brandEntries(WIKIPEDIA, [14907]), + ...brandEntries(PAYPAL, [17012, 26101]), + ...brandEntries(SALESFORCE, [14340]), + ...brandEntries(YOUTUBE, [36040, 43515]), + ...brandEntries(NETFLIX, [2906, 40027]), + ...brandEntries(TWITCH, [46489]), + ...brandEntries(TIKTOK, [396986, 138699]), + ...brandEntries(SPOTIFY, [8403, 34081]), + ...brandEntries(STEAM, [32590]), + ...brandEntries(BLIZZARD, [57976]), + ...brandEntries(EPIC, [395701]), + ...brandEntries(RIOT, [6507, 62830]), + ...brandEntries(PLAYSTATION, [33353]), + ...brandEntries(ROBLOX, [22697]), + ...brandEntries(UBISOFT, [197922]), + ...brandEntries(DISCORD, [49544, 394141]), + ...brandEntries(TELEGRAM, [62041, 59930, 211157]), + ...brandEntries(ZOOM, [30103]), + ...brandEntries(CHATGPT, [401115, 400645]), + ...brandEntries(QUAD9, [19281]), + ...brandEntries(OPENDNS, [36692]), ]) const ASN_HQ_COUNTRY = new Map([ - [13335, "US"], - [209242, "US"], - [54113, "US"], - [20940, "US"], - [16509, "US"], - [14618, "US"], - [8075, "US"], - [15169, "US"], - [32590, "US"], - [57976, "US"], - [2906, "US"], - [40027, "US"], - [36040, "US"], - [46489, "US"], - [401115, "US"], - [49544, "US"], - [32934, "US"], - [13238, "RU"], - [62041, "NL"], - [59930, "NL"], - [211157, "NL"], + ...hqEntries("US", [ + 13335, 209242, 54113, 20940, 16625, 32787, 35994, 16702, 24319, + 16509, 14618, 8987, 7224, 8075, 8068, 8069, 8070, 15169, 396982, + 32590, 57976, 2906, 40027, 36040, 43515, 46489, 401115, 400645, + 49544, 394141, 32934, 63293, 54115, 714, 6185, 36459, 54876, 14061, + 31898, 63949, 20473, 36351, 13414, 14413, 40793, 394706, 19679, 19750, + 14907, 17012, 26101, 14340, 30103, 36692, 395701, 6507, 62830, 33353, 22697, + ]), + ...hqEntries("IE", [9059]), + ...hqEntries("SG", [138699]), + ...hqEntries("DE", [24940, 213230]), + ...hqEntries("FR", [16276, 12876, 197922]), + ...hqEntries("CN", [45102, 132203]), + ...hqEntries("LU", [199524]), + ...hqEntries("CZ", [60068]), + ...hqEntries("RU", [13238, 50340, 49505, 9123, 198610, 57724, 47541, 47764]), + ...hqEntries("SE", [8403, 34081]), + ...hqEntries("NL", [62041, 59930, 211157]), + ...hqEntries("CH", [19281]), ]) -const GOOGLE: BrandHit = { service: "Google", category: "Веб" } -const CLOUDFLARE: BrandHit = { service: "Cloudflare", category: "CDN" } -const YOUTUBE: BrandHit = { service: "YouTube", category: "Видео / стриминг" } - const CIDR_BRANDS: Array<{ cidr: string; prefixLen: number; hit: BrandHit }> = [ { cidr: "104.16.0.0/13", prefixLen: 13, hit: CLOUDFLARE }, { cidr: "104.24.0.0/14", prefixLen: 14, hit: CLOUDFLARE }, @@ -75,8 +178,25 @@ const CIDR_BRANDS: Array<{ cidr: string; prefixLen: number; hit: BrandHit }> = [ { cidr: "208.117.224.0/19", prefixLen: 19, hit: YOUTUBE }, ].sort((a, b) => b.prefixLen - a.prefixLen) +const HOLDER_BRANDS: Array<{ re: RegExp; hit: BrandHit }> = [ + { re: /youtube/i, hit: YOUTUBE }, + { re: /valve|\bsteam\b/i, hit: STEAM }, + { re: /blizzard|battle.?net/i, hit: BLIZZARD }, + { re: /openai/i, hit: CHATGPT }, + { re: /riot games/i, hit: RIOT }, + { re: /epic games/i, hit: EPIC }, + { re: /\bapple\b/i, hit: APPLE }, + { re: /github/i, hit: GITHUB }, + { re: /spotify/i, hit: SPOTIFY }, + { re: /twitter|\bx corp\b/i, hit: X }, + { re: /dropbox/i, hit: DROPBOX }, + { re: /akamai/i, hit: AKAMAI }, +] + const NON_ISO = new Set(["EU", "AP", "ZZ", "XX", "A1", "A2", "O1"]) +const STEAM_ASN = 32590 + export function isIsoCountry(code: string): boolean { const c = String(code ?? "").trim().toUpperCase() return /^[A-Z]{2}$/.test(c) && !NON_ISO.has(c) @@ -114,10 +234,51 @@ export function brandByCidr(ip: string): BrandHit | null { return null } +export function brandByHolder(holder: string): BrandHit | null { + const h = String(holder ?? "").trim() + if (!h) return null + for (const row of HOLDER_BRANDS) { + if (row.re.test(h)) return row.hit + } + return null +} + +/** Игровые порты Steam — только вместе с AS32590, никогда :80/:443. */ +export function isSteamGamePort(proto: number, dstPort: number, srcPort: number): boolean { + if (proto !== 6 && proto !== 17) return false + const port = dstPort || srcPort + if (!port || port === 80 || port === 443) return false + if (port === 4380 || port === 3478) return true + return port >= 27000 && port <= 27100 +} + export function lookupBrand(ip: string, asn: number): BrandHit | null { return brandByCidr(ip) || brandByAsn(asn) } +/** + * Cloudflare CIDR бьёт holder (витрина на CF не становится Steam). + * Holder (YouTube и др.) бьёт остальные CIDR/ASN. + * Порты Steam — только AS32590 и не выше Cloudflare CIDR. + */ +export function resolveFlowBrand( + ip: string, + asn: number, + holder: string, + proto = 0, + dstPort = 0, + srcPort = 0, +): BrandHit | null { + const cidrBrand = brandByCidr(ip) + if (cidrBrand?.service === "Cloudflare") return cidrBrand + const holderBrand = brandByHolder(holder) + if (holderBrand) return holderBrand + const fromLookup = cidrBrand || brandByAsn(asn) + if (fromLookup) return fromLookup + if (asn === STEAM_ASN && isSteamGamePort(proto, dstPort, srcPort)) return STEAM + return null +} + const SKIP_MAP_SERVICES = new Set([ OTHER_SERVICE, "GRE", diff --git a/backend/src/services/traffic-flow-classify.test.ts b/backend/src/services/traffic-flow-classify.test.ts index 9d8d7b5..6b3ad03 100644 --- a/backend/src/services/traffic-flow-classify.test.ts +++ b/backend/src/services/traffic-flow-classify.test.ts @@ -53,6 +53,71 @@ const youtube = classifyFlowDst("173.194.160.163", 6, 443, 1, { assert.equal(youtube.service, "YouTube") assert.equal(youtube.category, "Видео / стриминг") +const valve = classifyFlowDst("203.0.113.40", 17, 27015, 50000, { + prefix: "203.0.113.0/24", + asn: 64501, + country: "US", + lat: null, + lng: null, + holder: "VALVE-CORPORATION", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(valve.service, "Steam") +assert.equal(valve.category, "Игры") + +const openaiHolder = classifyFlowDst("203.0.113.41", 6, 443, 1, { + prefix: "203.0.113.0/24", + asn: 64502, + country: "US", + lat: null, + lng: null, + holder: "OPENAI, US", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(openaiHolder.service, "ChatGPT") +assert.equal(openaiHolder.category, "ИИ") + +const cfNotSteam = classifyFlowDst("104.18.35.51", 6, 443, 1, { + prefix: "104.18.0.0/16", + asn: 32590, + country: "US", + lat: null, + lng: null, + holder: "VALVE-CORPORATION", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(cfNotSteam.service, "Cloudflare") +assert.notEqual(cfNotSteam.service, "Steam") + +const awsIeu = classifyFlowDst("203.0.113.42", 6, 443, 1, { + prefix: "203.0.113.0/24", + asn: 9059, + country: "IE", + lat: null, + lng: null, + holder: "AMAZON-02", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(awsIeu.service, "AWS") +assert.equal(awsIeu.category, "CDN") + +const googleCloud = classifyFlowDst("203.0.113.43", 6, 443, 1, { + prefix: "203.0.113.0/24", + asn: 396982, + country: "US", + lat: null, + lng: null, + holder: "GOOGLE-CLOUD", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(googleCloud.service, "Google") +assert.equal(googleCloud.category, "Веб") + const gre = classifyFlowDst("198.51.100.1", 47, 0, 0, null) assert.equal(gre.service, "GRE") assert.equal(gre.category, "Туннель") diff --git a/backend/src/services/traffic-flow-classify.ts b/backend/src/services/traffic-flow-classify.ts index 65bd1a4..0b589d0 100644 --- a/backend/src/services/traffic-flow-classify.ts +++ b/backend/src/services/traffic-flow-classify.ts @@ -1,4 +1,4 @@ -import { lookupBrand, OTHER_SERVICE } from "./traffic-flow-brands.js" +import { OTHER_SERVICE, resolveFlowBrand } from "./traffic-flow-brands.js" import { db } from "../db/index.js" import { evobgpSettings } from "../db/schema.js" import { ipInCidrV4, parseCidrV4 } from "./traffic-flow-ip.js" @@ -47,12 +47,13 @@ export function seedFlowCatalogForTests(input: { export function categoryFromPurpose(purpose: string, proto: number, dstPort: number, srcPort: number): string { const p = purpose.toLowerCase() - if (/gaming|steam|epic|riot/.test(p)) return "Игры" - if (/streaming|youtube|netflix|twitch|video/.test(p)) return "Видео / стриминг" - if (/cdn|cloudflare|akamai|fastly/.test(p)) return "CDN" + if (/gaming|steam|epic|riot|playstation|roblox|ubisoft/.test(p)) return "Игры" + if (/streaming|youtube|netflix|twitch|video|spotify/.test(p)) return "Видео / стриминг" + if (/cdn|cloudflare|akamai|fastly|hetzner|ovh|apple/.test(p)) return "CDN" if (/voip|discord|zoom/.test(p)) return "Голос" if (/openai|chatgpt|\bai\b/.test(p)) return "ИИ" - if (/веб|web|google/.test(p)) return "Веб" + if (/quad9|opendns/.test(p)) return "DNS" + if (/веб|web|google|github|paypal|vk|linkedin/.test(p)) return "Веб" const app = applicationName(proto, dstPort, srcPort) if (app === "DNS" || app === "SSH" || app === "BGP") return app if (app === "GRE" || app === "ESP" || app === "WireGuard") return "Туннель" @@ -79,10 +80,7 @@ export function classifyFlowDst( if (app === "WireGuard") return { service: "WireGuard", category: "Туннель" } const hit = matchCidr(dst) const holder = ripe?.holder ?? "" - const youtubeHolder = /youtube/i.test(holder) - const brand = youtubeHolder - ? { service: "YouTube", category: "Видео / стриминг" } - : lookupBrand(dst, ripe?.asn ?? 0) + const brand = resolveFlowBrand(dst, ripe?.asn ?? 0, holder, proto, dstPort, srcPort) const asnName = ripe?.asn ? asnPurpose.get(ripe.asn) : undefined const service = (hit?.purpose || brand?.service || asnName || OTHER_SERVICE).trim() || OTHER_SERVICE const category = hit diff --git a/backend/src/services/traffic-flow-map-hops.ts b/backend/src/services/traffic-flow-map-hops.ts index b7a71aa..8b762b8 100644 --- a/backend/src/services/traffic-flow-map-hops.ts +++ b/backend/src/services/traffic-flow-map-hops.ts @@ -5,8 +5,8 @@ import { userInterfaceBindings } from "../db/schema.js" import { applicationName, flowRowMatchesFilter } from "./traffic-flow-apps.js" import { isNamedInternetService, - lookupBrand, mapServiceNodeId, + resolveFlowBrand, } from "./traffic-flow-brands.js" import { dedupFlowRowsMaxBytes } from "./traffic-flow-dedup.js" import { getFlowListenerState, listFlowRowsForWindow } from "./traffic-flow-ingest.js" @@ -174,10 +174,7 @@ function classifyMapDstLite( if (proto === 47 || proto === 50) return null const app = applicationName(proto, dstPort, srcPort) if (app === "WireGuard" || app === "DNS" || app === "SSH" || app === "BGP") return null - if (/youtube/i.test(ripe?.holder ?? "")) { - return { service: "YouTube", category: "Видео / стриминг" } - } - const brand = lookupBrand(dst, ripe?.asn ?? 0) + const brand = resolveFlowBrand(dst, ripe?.asn ?? 0, ripe?.holder ?? "", proto, dstPort, srcPort) if (!brand || !isNamedInternetService(brand.service, brand.category)) return null return brand }