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 (