Docker images / prepare-release (push) Successful in 8s
Docker images / backend-test (push) Successful in 2m29s
Docker images / frontend-image (push) Successful in 3m20s
Docker images / updater-image (push) Successful in 51s
Docker images / backend-image (push) Successful in 2m40s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 13s
- Introduced `canonicalIp` function to standardize IP address formats across the application, improving consistency in flow processing. - Updated traffic flow analytics to utilize new endpoint resolution logic, enhancing accuracy in traffic classification. - Enhanced tests for traffic flow analytics and IP handling, ensuring comprehensive coverage for new functionalities. - Improved traffic flow destination resolution with additional test cases for various internet services, including Google and Fastly. Co-authored-by: Cursor <cursoragent@cursor.com>
132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import { applicationName } from "./traffic-flow-apps.js"
|
|
import { isIsoCountry, isNamedInternetService, OTHER_SERVICE, resolveFlowBrand } from "./traffic-flow-brands.js"
|
|
import { classifyFlowDst, type FlowClassification } from "./traffic-flow-classify.js"
|
|
import { resolveFlowIp } from "./traffic-flow-geoip.js"
|
|
import { canonicalFactIface } from "./traffic-flow-ifindex.js"
|
|
import {
|
|
resolveFlowEndpoints,
|
|
type FlowEndpoints,
|
|
type InternetDestCtx,
|
|
} from "./traffic-flow-ip.js"
|
|
import type { FlowIpMeta } from "./traffic-flow-ripe.js"
|
|
import {
|
|
flowOursHosts,
|
|
resolveClient,
|
|
type FlowTopology,
|
|
} from "./traffic-flow-topology.js"
|
|
|
|
export interface InternetDestMeta {
|
|
dest: string
|
|
ripe: FlowIpMeta | null
|
|
classified: FlowClassification
|
|
country: string
|
|
asn: number
|
|
endpoints: FlowEndpoints
|
|
}
|
|
|
|
export function destCtxForIface(
|
|
topo: FlowTopology | null | undefined,
|
|
serverId: number,
|
|
inIface: string,
|
|
nat?: Pick<InternetDestCtx, "natSrc" | "natDst" | "natSrcPort" | "natDstPort">,
|
|
): InternetDestCtx {
|
|
const name = canonicalFactIface(serverId, inIface) || String(inIface ?? "").trim()
|
|
return {
|
|
ours: flowOursHosts(topo),
|
|
boundClient: Boolean(
|
|
topo && name && (
|
|
resolveClient(topo, serverId, name)
|
|
|| topo.clientIfaces.get(serverId)?.has(name)
|
|
),
|
|
),
|
|
natSrc: nat?.natSrc,
|
|
natDst: nat?.natDst,
|
|
natSrcPort: nat?.natSrcPort,
|
|
natDstPort: nat?.natDstPort,
|
|
}
|
|
}
|
|
|
|
const OTHER_BRAND: FlowClassification = { service: OTHER_SERVICE, category: OTHER_SERVICE }
|
|
|
|
function isTunnelProto(proto: number, dstPort: number, srcPort: number): boolean {
|
|
if (proto === 47 || proto === 50) return true
|
|
return applicationName(proto, dstPort, srcPort) === "WireGuard"
|
|
}
|
|
|
|
/** Бренд интернет-dest: ASN/CIDR до skip DNS. GRE/ESP/WG — не сервис. */
|
|
export function classifyInternetBrand(
|
|
dst: string,
|
|
proto: number,
|
|
dstPort: number,
|
|
srcPort: number,
|
|
ripe: FlowIpMeta | null,
|
|
): FlowClassification | null {
|
|
if (isTunnelProto(proto, dstPort, srcPort)) return null
|
|
const brand = resolveFlowBrand(dst, ripe?.asn ?? 0, ripe?.holder ?? "", proto, dstPort, srcPort)
|
|
if (brand && isNamedInternetService(brand.service, brand.category)) return brand
|
|
const app = applicationName(proto, dstPort, srcPort)
|
|
if (app === "DNS" || app === "SSH" || app === "BGP") return null
|
|
return null
|
|
}
|
|
|
|
/** Тот же классификатор, что аналитика (GeoLite2 ASN + catalog). Туннель → Прочее. */
|
|
export function mapInternetBrand(
|
|
dst: string,
|
|
proto: number,
|
|
dstPort: number,
|
|
srcPort: number,
|
|
ripe: FlowIpMeta | null,
|
|
): FlowClassification {
|
|
if (isTunnelProto(proto, dstPort, srcPort)) return OTHER_BRAND
|
|
const classified = classifyFlowDst(dst, proto, dstPort, srcPort, ripe, { ignoreTunnelProto: true })
|
|
if (isNamedInternetService(classified.service, classified.category)) return classified
|
|
return OTHER_BRAND
|
|
}
|
|
|
|
export function resolveInternetDest(opts: {
|
|
src: string
|
|
dst: string
|
|
proto: number
|
|
srcPort: number
|
|
dstPort: number
|
|
serverId: number
|
|
inIface: string
|
|
topo?: FlowTopology | null
|
|
natSrc?: string
|
|
natDst?: string
|
|
natSrcPort?: number
|
|
natDstPort?: number
|
|
}): InternetDestMeta {
|
|
const ctx = destCtxForIface(opts.topo, opts.serverId, opts.inIface, {
|
|
natSrc: opts.natSrc,
|
|
natDst: opts.natDst,
|
|
natSrcPort: opts.natSrcPort,
|
|
natDstPort: opts.natDstPort,
|
|
})
|
|
const endpoints = resolveFlowEndpoints({
|
|
src: opts.src,
|
|
dst: opts.dst,
|
|
srcPort: opts.srcPort,
|
|
dstPort: opts.dstPort,
|
|
ctx,
|
|
})
|
|
const dest = endpoints.internetPeer
|
|
if (!dest) {
|
|
return { dest: "", ripe: null, classified: OTHER_BRAND, country: "", asn: 0, endpoints }
|
|
}
|
|
const ripe = resolveFlowIp(dest)
|
|
const classified = classifyFlowDst(
|
|
dest,
|
|
opts.proto,
|
|
endpoints.peerPort,
|
|
endpoints.otherPort,
|
|
ripe,
|
|
{ ignoreTunnelProto: true },
|
|
)
|
|
const country = ripe?.ok && isIsoCountry(ripe.country)
|
|
? ripe.country
|
|
: (ripe?.ok ? "" : "unknown")
|
|
const asn = ripe?.ok && ripe.asn ? ripe.asn : 0
|
|
return { dest, ripe, classified, country, asn, endpoints }
|
|
}
|