PeerCortex unifies PeeringDB, RIPE Stat, bgproutes.io, RIPE Atlas, Route Views, IRR, RPKI, and CAIDA into a single AI-queryable MCP Server for network engineers. Powered by local Ollama. Core capabilities: - 34 MCP tools for network intelligence - 11 data sources unified - ASPA validation engine (RFC 9582) with leak detection - Peering partner discovery with AI-ranked matches - BGP analysis and anomaly detection - RPKI monitoring and compliance reports - Latency/traceroute via RIPE Atlas - Transit analysis and cost comparison - IX traffic statistics - AS topology mapping - ASPA object generator and simulator - 100% local AI — no cloud dependencies
323 lines
9.9 KiB
TypeScript
323 lines
9.9 KiB
TypeScript
/**
|
|
* @module mcp-server/tools/security
|
|
* MCP Tool: BGP security — hijack detection, route leak analysis (ASPA-based),
|
|
* bogon filtering, and blacklist checks.
|
|
*
|
|
* Provides security-focused analysis of BGP routing using RPKI ROV and ASPA
|
|
* validation from bgproutes.io, combined with historical data from RIPE Stat
|
|
* and other sources.
|
|
*/
|
|
|
|
import { z } from "zod";
|
|
import type { ASN, AnomalySeverity } from "../../types/common.js";
|
|
|
|
// ── Tool Schemas ─────────────────────────────────────────
|
|
|
|
/** Input schema for hijack detection */
|
|
export const hijackDetectionSchema = z.object({
|
|
prefix: z
|
|
.string()
|
|
.describe("IP prefix to check for hijacks (e.g., '1.1.1.0/24')"),
|
|
expectedOriginAsn: z
|
|
.number()
|
|
.optional()
|
|
.describe("Expected origin ASN (if known)"),
|
|
includeHistory: z
|
|
.boolean()
|
|
.optional()
|
|
.default(true)
|
|
.describe("Include historical hijack events"),
|
|
});
|
|
|
|
/** Input schema for ASPA-based route leak detection */
|
|
export const routeLeakDetectionSchema = z.object({
|
|
asn: z
|
|
.union([z.string(), z.number()])
|
|
.describe("ASN to check for route leaks involving their prefixes"),
|
|
prefixes: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("Specific prefixes to check (default: all announced by ASN)"),
|
|
timeRange: z
|
|
.object({
|
|
start: z.string().describe("Start time (ISO 8601)"),
|
|
end: z.string().describe("End time (ISO 8601)"),
|
|
})
|
|
.optional()
|
|
.describe("Time range to search for leaks"),
|
|
});
|
|
|
|
/** Input schema for bogon check */
|
|
export const bogonCheckSchema = z.object({
|
|
asn: z
|
|
.union([z.string(), z.number()])
|
|
.describe("ASN to check for bogon origination or path issues"),
|
|
includeReserved: z
|
|
.boolean()
|
|
.optional()
|
|
.default(true)
|
|
.describe("Check for reserved/unallocated prefix announcements"),
|
|
includeBogonAsns: z
|
|
.boolean()
|
|
.optional()
|
|
.default(true)
|
|
.describe("Check for bogon ASNs in AS paths"),
|
|
});
|
|
|
|
/** Input schema for blacklist check */
|
|
export const blacklistCheckSchema = z.object({
|
|
resource: z
|
|
.string()
|
|
.describe("IP address, prefix, or ASN to check against blacklists"),
|
|
lists: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("Specific blacklists to check (default: all known)"),
|
|
});
|
|
|
|
// ── Result Types ─────────────────────────────────────────
|
|
|
|
/** Detected BGP hijack */
|
|
export interface HijackEvent {
|
|
readonly prefix: string;
|
|
readonly legitimateOrigin: ASN;
|
|
readonly hijackerAsn: ASN;
|
|
readonly hijackerName: string;
|
|
readonly type: "exact-match" | "sub-prefix" | "squat";
|
|
readonly severity: AnomalySeverity;
|
|
readonly rpkiStatus: "valid" | "invalid" | "not-found" | "unknown";
|
|
readonly firstSeen: string;
|
|
readonly lastSeen: string;
|
|
readonly affectedPaths: number;
|
|
readonly description: string;
|
|
}
|
|
|
|
/** Hijack detection result */
|
|
export interface HijackDetectionResult {
|
|
readonly prefix: string;
|
|
readonly expectedOriginAsn: ASN | null;
|
|
readonly currentOrigins: ReadonlyArray<{
|
|
readonly asn: ASN;
|
|
readonly name: string;
|
|
readonly rpkiValid: boolean;
|
|
}>;
|
|
readonly activeHijacks: ReadonlyArray<HijackEvent>;
|
|
readonly historicalHijacks: ReadonlyArray<HijackEvent>;
|
|
readonly riskLevel: AnomalySeverity;
|
|
readonly recommendations: ReadonlyArray<string>;
|
|
}
|
|
|
|
/** ASPA-based route leak event */
|
|
export interface RouteLeakEvent {
|
|
readonly prefix: string;
|
|
readonly leakerAsn: ASN;
|
|
readonly leakerName: string;
|
|
readonly leakedTo: ReadonlyArray<ASN>;
|
|
readonly aspaValidation: {
|
|
readonly state: "invalid";
|
|
readonly violatingHop: {
|
|
readonly asn: ASN;
|
|
readonly position: number;
|
|
readonly reason: string;
|
|
};
|
|
};
|
|
readonly severity: AnomalySeverity;
|
|
readonly firstSeen: string;
|
|
readonly lastSeen: string;
|
|
readonly description: string;
|
|
}
|
|
|
|
/** Route leak detection result */
|
|
export interface RouteLeakDetectionResult {
|
|
readonly asn: ASN;
|
|
readonly name: string;
|
|
readonly prefixesChecked: number;
|
|
readonly activeLeaks: ReadonlyArray<RouteLeakEvent>;
|
|
readonly historicalLeaks: ReadonlyArray<RouteLeakEvent>;
|
|
readonly aspaStatus: {
|
|
readonly hasAspaObjects: boolean;
|
|
readonly aspaObjectCount: number;
|
|
readonly coveragePercent: number;
|
|
};
|
|
readonly riskLevel: AnomalySeverity;
|
|
readonly recommendations: ReadonlyArray<string>;
|
|
}
|
|
|
|
/** Bogon check finding */
|
|
export interface BogonFinding {
|
|
readonly type: "bogon_prefix" | "bogon_asn" | "reserved_prefix" | "unallocated_prefix";
|
|
readonly resource: string;
|
|
readonly severity: AnomalySeverity;
|
|
readonly description: string;
|
|
readonly asPath: ReadonlyArray<ASN>;
|
|
}
|
|
|
|
/** Bogon check result */
|
|
export interface BogonCheckResult {
|
|
readonly asn: ASN;
|
|
readonly findings: ReadonlyArray<BogonFinding>;
|
|
readonly bogonPrefixCount: number;
|
|
readonly bogonAsnInPathCount: number;
|
|
readonly clean: boolean;
|
|
readonly recommendations: ReadonlyArray<string>;
|
|
}
|
|
|
|
/** Blacklist check result */
|
|
export interface BlacklistCheckResult {
|
|
readonly resource: string;
|
|
readonly listed: boolean;
|
|
readonly listings: ReadonlyArray<{
|
|
readonly list: string;
|
|
readonly listedSince: string;
|
|
readonly reason: string;
|
|
readonly delistUrl: string;
|
|
}>;
|
|
readonly clean: boolean;
|
|
}
|
|
|
|
// ── Tool Handlers ────────────────────────────────────────
|
|
|
|
/**
|
|
* Detect active and historical BGP hijacks for a prefix.
|
|
*
|
|
* Checks for unauthorized origin ASes, sub-prefix hijacks, and route
|
|
* squatting using RPKI ROV validation and MOAS conflict detection.
|
|
*
|
|
* @param input - Validated detection parameters
|
|
* @returns Hijack detection result with active/historical events
|
|
*
|
|
* @example
|
|
* ```
|
|
* > Check if 1.1.1.0/24 is being hijacked
|
|
*
|
|
* Returns: Current origins, RPKI validation, any MOAS conflicts,
|
|
* and historical hijack events.
|
|
* ```
|
|
*/
|
|
export async function handleHijackDetection(
|
|
input: z.infer<typeof hijackDetectionSchema>
|
|
): Promise<HijackDetectionResult> {
|
|
// TODO: Query bgproutes.io RIB for current origins of this prefix
|
|
// TODO: Validate each origin via RPKI ROV
|
|
// TODO: Compare with expected origin if provided
|
|
// TODO: Check for sub-prefix announcements (more-specific hijacks)
|
|
// TODO: Query historical data from RIPE Stat BGP updates
|
|
|
|
return {
|
|
prefix: input.prefix,
|
|
expectedOriginAsn: input.expectedOriginAsn ?? null,
|
|
currentOrigins: [],
|
|
activeHijacks: [],
|
|
historicalHijacks: [],
|
|
riskLevel: "info",
|
|
recommendations: [
|
|
"Create ROAs for all your prefixes to enable RPKI-based hijack protection",
|
|
"Monitor BGP announcements in real-time for early hijack detection",
|
|
"Register ASPA objects to protect against route leaks",
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detect route leaks using ASPA validation.
|
|
*
|
|
* Analyzes BGP paths for ASPA validation failures, which indicate
|
|
* potential route leaks where a customer or peer re-announces routes
|
|
* to unauthorized neighbors.
|
|
*
|
|
* ASPA (Autonomous System Provider Authorization) objects declare which
|
|
* ASes are authorized providers. A path that violates these declarations
|
|
* indicates a route leak.
|
|
*
|
|
* @param input - Validated detection parameters
|
|
* @returns Route leak detection result with ASPA analysis
|
|
*
|
|
* @example
|
|
* ```
|
|
* > Detect route leaks using ASPA validation for prefixes of AS13335
|
|
*
|
|
* Returns: Prefixes with ASPA-invalid paths, the leaking AS, severity,
|
|
* and recommendations for deploying ASPA objects.
|
|
* ```
|
|
*/
|
|
export async function handleRouteLeakDetection(
|
|
input: z.infer<typeof routeLeakDetectionSchema>
|
|
): Promise<RouteLeakDetectionResult> {
|
|
// TODO: Get all announced prefixes for the ASN (or use provided list)
|
|
// TODO: For each prefix, query bgproutes.io RIB entries with ASPA validation
|
|
// TODO: Filter for entries where aspaValidation.state === "invalid"
|
|
// TODO: Identify the leaking AS (the hop that violates ASPA)
|
|
// TODO: Query historical updates for past leak events
|
|
// TODO: Check if the ASN has registered ASPA objects
|
|
|
|
const _input = input;
|
|
|
|
return {
|
|
asn: 0, // TODO: parseASN(input.asn)
|
|
name: "",
|
|
prefixesChecked: 0,
|
|
activeLeaks: [],
|
|
historicalLeaks: [],
|
|
aspaStatus: {
|
|
hasAspaObjects: false,
|
|
aspaObjectCount: 0,
|
|
coveragePercent: 0,
|
|
},
|
|
riskLevel: "info",
|
|
recommendations: [
|
|
"Register ASPA objects to declare your authorized upstream providers",
|
|
"Monitor ASPA validation results for your prefixes in real-time",
|
|
"Coordinate with your upstreams to ensure they also deploy ASPA",
|
|
"Use bgproutes.io real-time stream for immediate leak alerting",
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check for bogon prefixes and bogon ASNs in routing.
|
|
*
|
|
* @param input - Validated check parameters
|
|
* @returns Bogon check result with findings
|
|
*/
|
|
export async function handleBogonCheck(
|
|
input: z.infer<typeof bogonCheckSchema>
|
|
): Promise<BogonCheckResult> {
|
|
// TODO: Get all announced prefixes for the ASN
|
|
// TODO: Check against IANA reserved/special-purpose ranges
|
|
// TODO: Check AS paths for bogon ASNs (reserved, documentation, private)
|
|
// TODO: Check for unallocated prefixes from RIR data
|
|
|
|
const _input = input;
|
|
|
|
return {
|
|
asn: 0,
|
|
findings: [],
|
|
bogonPrefixCount: 0,
|
|
bogonAsnInPathCount: 0,
|
|
clean: true,
|
|
recommendations: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check a resource against known blacklists and reputation databases.
|
|
*
|
|
* @param input - Validated check parameters
|
|
* @returns Blacklist check result with listings
|
|
*/
|
|
export async function handleBlacklistCheck(
|
|
input: z.infer<typeof blacklistCheckSchema>
|
|
): Promise<BlacklistCheckResult> {
|
|
// TODO: Check against Spamhaus (DROP, EDROP, ASN-DROP)
|
|
// TODO: Check against Team Cymru bogon lists
|
|
// TODO: Check against various DNSBL services
|
|
// TODO: Aggregate results from specified or all lists
|
|
|
|
return {
|
|
resource: input.resource,
|
|
listed: false,
|
|
listings: [],
|
|
clean: true,
|
|
};
|
|
}
|