From f3c846201ce2f09de0b36074d8984170086b9715 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 12 Sep 2026 11:20:16 +0700 Subject: [PATCH] feat(traffic-flow): enhance Instagram service handling and classification - Added support for Instagram in traffic flow resolution and classification, including new test cases to validate its behavior. - Introduced `INSTAGRAM` brand handling in the traffic flow logic, ensuring accurate service identification for Instagram-related traffic. - Updated existing tests to cover various scenarios involving Instagram, including IP resolution and service categorization. - Enhanced the service brand icon component to include an Instagram icon for better visual representation. Co-authored-by: Cursor --- .../src/services/traffic-flow-brands.test.ts | 27 ++++++++++++ backend/src/services/traffic-flow-brands.ts | 42 +++++++++++++++--- .../services/traffic-flow-classify.test.ts | 44 +++++++++++++++++++ backend/src/services/traffic-flow-classify.ts | 2 +- components/network-map/service-brand-icon.tsx | 8 ++++ 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/backend/src/services/traffic-flow-brands.test.ts b/backend/src/services/traffic-flow-brands.test.ts index d685083..2b70a27 100644 --- a/backend/src/services/traffic-flow-brands.test.ts +++ b/backend/src/services/traffic-flow-brands.test.ts @@ -79,6 +79,33 @@ assert.equal(resolveFlowBrand("64.233.161.1", 0, "", 17, 443, 50000)?.service, " assert.equal(resolveFlowBrand("64.233.161.1", 0, "", 6, 80, 50000)?.service, "Google") assert.equal(resolveFlowBrand("2001:4860:4860::8888", 15169, "GOOGLE", 17, 53, 53000)?.service, "Google") assert.equal(resolveFlowBrand("2001:4860:4860::8888", 15169, "GOOGLE", 17, 443, 50000)?.service, "YouTube") + +assert.equal(brandByAsn(32934)?.service, "Meta") +assert.equal(lookupBrand("157.240.12.52", 0)?.service, "Meta") +assert.equal(lookupBrand("57.144.22.192", 0)?.service, "Meta") +assert.equal(brandByHolder("Instagram LLC")?.service, "Instagram") +assert.equal(mapServiceNodeId("Instagram"), "svc:instagram") +assert.equal(isNamedInternetService("Instagram", "Видео / стриминг"), true) +assert.equal( + resolveFlowBrand("157.240.12.52", 32934, "FACEBOOK", 6, 443, 51234)?.service, + "Instagram", + "HTTPS на Meta front → Instagram, как YouTube на Google", +) +assert.equal( + resolveFlowBrand("57.144.22.192", 0, "", 17, 443, 50000)?.service, + "Instagram", + "cdninstagram CIDR :443 без ASN → Instagram", +) +assert.equal( + resolveFlowBrand("157.240.12.52", 32934, "FACEBOOK", 6, 80, 50000)?.service, + "Meta", + ":80 на Meta остаётся Meta", +) +assert.equal( + resolveFlowBrand("157.240.1.1", 54115, "WHATSAPP", 6, 443, 1)?.service, + "Meta", + "AS54115 WhatsApp не становится Instagram", +) assert.equal(resolveRipeCountry("", 9059, ""), "IE") assert.equal(resolveRipeCountry("", 24940, ""), "DE") diff --git a/backend/src/services/traffic-flow-brands.ts b/backend/src/services/traffic-flow-brands.ts index 8d4e03e..5d8c50c 100644 --- a/backend/src/services/traffic-flow-brands.ts +++ b/backend/src/services/traffic-flow-brands.ts @@ -39,6 +39,7 @@ 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 INSTAGRAM: BrandHit = { service: "Instagram", ...VIDEO } const GOOGLE: BrandHit = { service: "Google", ...WEB } const GITHUB: BrandHit = { service: "GitHub", ...WEB } @@ -184,10 +185,27 @@ const CIDR_BRANDS: Array<{ cidr: string; prefixLen: number; hit: BrandHit }> = [ { cidr: "216.239.32.0/19", prefixLen: 19, hit: GOOGLE }, { cidr: "208.65.152.0/22", prefixLen: 22, hit: YOUTUBE }, { cidr: "208.117.224.0/19", prefixLen: 19, hit: YOUTUBE }, + { cidr: "31.13.64.0/18", prefixLen: 18, hit: META }, + { cidr: "57.141.0.0/16", prefixLen: 16, hit: META }, + { cidr: "57.142.0.0/15", prefixLen: 15, hit: META }, + { cidr: "57.144.0.0/14", prefixLen: 14, hit: META }, + { cidr: "57.148.0.0/15", prefixLen: 15, hit: META }, + { cidr: "66.220.144.0/20", prefixLen: 20, hit: META }, + { cidr: "69.63.176.0/20", prefixLen: 20, hit: META }, + { cidr: "69.171.224.0/19", prefixLen: 19, hit: META }, + { cidr: "74.119.76.0/22", prefixLen: 22, hit: META }, + { cidr: "129.134.0.0/16", prefixLen: 16, hit: META }, + { cidr: "157.240.0.0/16", prefixLen: 16, hit: META }, + { cidr: "173.252.64.0/18", prefixLen: 18, hit: META }, + { cidr: "179.60.192.0/22", prefixLen: 22, hit: META }, + { cidr: "185.60.216.0/22", prefixLen: 22, hit: META }, + { cidr: "199.201.64.0/22", prefixLen: 22, hit: META }, + { cidr: "204.15.20.0/22", prefixLen: 22, hit: META }, ].sort((a, b) => b.prefixLen - a.prefixLen) const HOLDER_BRANDS: Array<{ re: RegExp; hit: BrandHit }> = [ { re: /youtube/i, hit: YOUTUBE }, + { re: /instagram/i, hit: INSTAGRAM }, { re: /valve|\bsteam\b/i, hit: STEAM }, { re: /blizzard|battle.?net/i, hit: BLIZZARD }, { re: /openai/i, hit: CHATGPT }, @@ -205,6 +223,9 @@ const NON_ISO = new Set(["EU", "AP", "ZZ", "XX", "A1", "A2", "O1"]) const STEAM_ASN = 32590 const GOOGLE_FRONT_ASN = new Set([15169, 396982]) +/** AS32934 / AS63293 — Meta front (Facebook + Instagram CDN). AS54115 — WhatsApp, не Instagram. */ +const META_FRONT_ASN = new Set([32934, 63293]) +const WHATSAPP_ASN = 54115 function isGooglePublicDns(ip: string): boolean { return ipInCidrV4(ip, "8.8.8.0/24") || ipInCidrV4(ip, "8.8.4.0/24") @@ -274,9 +295,19 @@ export function lookupBrand(ip: string, asn: number): BrandHit | null { return brandByCidr(ip) || brandByAsn(asn) } +function isGoogleFront(asn: number, cidrBrand: BrandHit | null, asnBrand: BrandHit | null): boolean { + return GOOGLE_FRONT_ASN.has(asn) || cidrBrand?.service === "Google" || asnBrand?.service === "Google" +} + +function isInstagramFront(asn: number, cidrBrand: BrandHit | null, asnBrand: BrandHit | null): boolean { + if (asn === WHATSAPP_ASN) return false + return META_FRONT_ASN.has(asn) || cidrBrand?.service === "Meta" || asnBrand?.service === "Meta" +} + /** * Cloudflare CIDR бьёт holder (витрина на CF не становится Steam). - * Holder (YouTube и др.) бьёт остальные CIDR/ASN. + * Holder (YouTube / Instagram и др.) бьёт остальные CIDR/ASN. + * HTTPS/QUIC на Google front → YouTube (кроме 8.8.8.8); на Meta front → Instagram (кроме WhatsApp ASN). * Порты Steam — только AS32590 и не выше Cloudflare CIDR. */ export function resolveFlowBrand( @@ -292,13 +323,12 @@ export function resolveFlowBrand( const holderBrand = brandByHolder(holder) if (holderBrand) return holderBrand const asnBrand = brandByAsn(asn) - if ( - !isGooglePublicDns(ip) - && isHttpsOrQuic(proto, dstPort, srcPort) - && (GOOGLE_FRONT_ASN.has(asn) || cidrBrand?.service === "Google" || asnBrand?.service === "Google") - ) { + if (!isGooglePublicDns(ip) && isHttpsOrQuic(proto, dstPort, srcPort) && isGoogleFront(asn, cidrBrand, asnBrand)) { return YOUTUBE } + if (isHttpsOrQuic(proto, dstPort, srcPort) && isInstagramFront(asn, cidrBrand, asnBrand)) { + return INSTAGRAM + } const fromLookup = cidrBrand || asnBrand if (fromLookup) return fromLookup if (asn === STEAM_ASN && isSteamGamePort(proto, dstPort, srcPort)) return STEAM diff --git a/backend/src/services/traffic-flow-classify.test.ts b/backend/src/services/traffic-flow-classify.test.ts index ecf3e6a..1bc201e 100644 --- a/backend/src/services/traffic-flow-classify.test.ts +++ b/backend/src/services/traffic-flow-classify.test.ts @@ -155,6 +155,50 @@ const ipv6Yt = classifyFlowDst("2001:4860:4860::8888", 17, 443, 50000, { fetchedAt: Date.now(), }) assert.equal(ipv6Yt.service, "YouTube") + +const instagram = classifyFlowDst("157.240.12.52", 6, 443, 62598, { + prefix: "157.240.0.0/16", + asn: 32934, + country: "US", + lat: null, + lng: null, + holder: "FACEBOOK", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(instagram.service, "Instagram") +assert.equal(instagram.category, "Видео / стриминг") +assert.notEqual(instagram.service, "Meta") + +const metaHttp = classifyFlowDst("157.240.12.52", 6, 80, 50000, { + prefix: "157.240.0.0/16", + asn: 32934, + country: "US", + lat: null, + lng: null, + holder: "FACEBOOK", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(metaHttp.service, "Meta") +assert.equal(metaHttp.category, "CDN") + +const igHolder = classifyFlowDst("203.0.113.80", 6, 443, 1, { + prefix: "203.0.113.0/24", + asn: 64503, + country: "US", + lat: null, + lng: null, + holder: "Instagram LLC", + ok: true, + fetchedAt: Date.now(), +}) +assert.equal(igHolder.service, "Instagram") + +const igCidr = classifyFlowDst("57.144.22.192", 17, 443, 50000, null) +assert.equal(igCidr.service, "Instagram") +assert.equal(igCidr.category, "Видео / стриминг") + const esp = classifyFlowDst("198.51.100.1", 50, 0, 0, null) assert.equal(esp.category, "Туннель") assert.equal(applicationName(17, 443, 50000), "QUIC") diff --git a/backend/src/services/traffic-flow-classify.ts b/backend/src/services/traffic-flow-classify.ts index 96c65e4..0a3e036 100644 --- a/backend/src/services/traffic-flow-classify.ts +++ b/backend/src/services/traffic-flow-classify.ts @@ -48,7 +48,7 @@ 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|playstation|roblox|ubisoft/.test(p)) return "Игры" - if (/streaming|youtube|netflix|twitch|video|spotify/.test(p)) return "Видео / стриминг" + if (/streaming|youtube|netflix|twitch|video|spotify|instagram/.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 "ИИ" diff --git a/components/network-map/service-brand-icon.tsx b/components/network-map/service-brand-icon.tsx index 945f9d6..60f3c22 100644 --- a/components/network-map/service-brand-icon.tsx +++ b/components/network-map/service-brand-icon.tsx @@ -74,6 +74,14 @@ export function ServiceBrandIcon({ label, size = 22 }: { label: string; size?: n ) + case "instagram": + return ( + + + + + + ) case "netflix": return (