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 <[email protected]>
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 "ИИ"
|
||||
|
||||
@@ -74,6 +74,14 @@ export function ServiceBrandIcon({ label, size = 22 }: { label: string; size?: n
|
||||
<path d="M10.2 9.2v5.6L15.6 12Z" fill="#fff" />
|
||||
</BrandSvg>
|
||||
)
|
||||
case "instagram":
|
||||
return (
|
||||
<BrandSvg size={size}>
|
||||
<rect x="3" y="3" width="18" height="18" rx="5" fill="#E4405F" />
|
||||
<circle cx="12" cy="12.2" r="4.1" fill="none" stroke="#fff" strokeWidth="1.8" />
|
||||
<circle cx="16.3" cy="7.7" r="1.15" fill="#fff" />
|
||||
</BrandSvg>
|
||||
)
|
||||
case "netflix":
|
||||
return (
|
||||
<BrandSvg size={size}>
|
||||
|
||||
Reference in New Issue
Block a user