Files
MikrotikManager/backend/src/services/traffic-flow-dest.ts
T
DenozordecandCursor ee9804f1bb
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
feat(traffic-flow): enhance flow analytics and IP handling
- 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>
2026-09-12 01:51:23 +07:00

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 }
}