fix: stabilize earth bgp geography and rendering

This commit is contained in:
linkong
2026-04-02 15:36:20 +08:00
parent 07e4f519a1
commit e5fec8ba3d
26 changed files with 1788 additions and 137 deletions

View File

@@ -55,6 +55,11 @@ def _unique_peers(events: list[dict[str, Any]]) -> list[int]:
return sorted(peers)
def _path_signature(metadata: dict[str, Any]) -> tuple[int, ...]:
path = metadata.get("as_path") or []
return tuple(int(asn) for asn in path if asn is not None)
def detect_origin_change_anomalies(
*,
source: str,
@@ -100,6 +105,7 @@ def detect_origin_change_anomalies(
)
sample_metadata = sample_event.get("metadata") or {}
sample_enrichment = sample_metadata.get("enrichment") or {}
sample_prefix_geography = sample_enrichment.get("prefix_geography") or {}
anomaly_type = "origin_change"
severity = "critical"
confidence = 0.86
@@ -140,8 +146,10 @@ def detect_origin_change_anomalies(
"origin_asn_profile": sample_enrichment.get("origin_asn_profile"),
"new_origin_asn_profile": sample_enrichment.get("new_origin_asn_profile"),
"rpki_validation": sample_enrichment.get("rpki_validation"),
"prefix_geography": sample_prefix_geography,
"prefix_scope": sample_enrichment.get("prefix_scope"),
"impacted_regions": related_regions
"impacted_regions": sample_prefix_geography.get("regions")
or related_regions
or sample_enrichment.get("prefix_scope", {}).get("regions", []),
},
)
@@ -180,6 +188,7 @@ def detect_more_specific_burst_anomalies(
sample = more_specifics[0].get("metadata") or {}
sample_enrichment = sample.get("enrichment") or {}
sample_prefix_geography = sample_enrichment.get("prefix_geography") or {}
event_count = len(more_specifics)
anomalies.append(
BGPAnomaly(
@@ -205,8 +214,10 @@ def detect_more_specific_burst_anomalies(
"unique_prefixes": unique_prefixes,
"rpki_validation": sample_enrichment.get("rpki_validation"),
"origin_asn_profile": sample_enrichment.get("origin_asn_profile"),
"prefix_geography": sample_prefix_geography,
"prefix_scope": sample_enrichment.get("prefix_scope"),
"impacted_regions": _iter_event_regions(more_specifics)
"impacted_regions": sample_prefix_geography.get("regions")
or _iter_event_regions(more_specifics)
or sample_enrichment.get("prefix_scope", {}).get("regions", []),
},
)
@@ -242,6 +253,7 @@ def detect_mass_withdrawal_anomalies(
sample_event = related_events[0] if related_events else {}
sample_metadata = sample_event.get("metadata") or {}
sample_enrichment = sample_metadata.get("enrichment") or {}
sample_prefix_geography = sample_enrichment.get("prefix_geography") or {}
severity = "medium"
if count >= 4 or len(related_collectors) >= 3:
severity = "high"
@@ -277,8 +289,175 @@ def detect_mass_withdrawal_anomalies(
],
"origin_asn_profile": sample_enrichment.get("origin_asn_profile"),
"rpki_validation": sample_enrichment.get("rpki_validation"),
"prefix_geography": sample_prefix_geography,
"prefix_scope": sample_enrichment.get("prefix_scope"),
"impacted_regions": _iter_event_regions(related_events)
"impacted_regions": sample_prefix_geography.get("regions")
or _iter_event_regions(related_events)
or sample_enrichment.get("prefix_scope", {}).get("regions", []),
},
)
)
return anomalies
def detect_route_leak_anomalies(
*,
source: str,
snapshot_id: int | None,
task_id: int | None,
events: list[dict[str, Any]],
) -> list[BGPAnomaly]:
events_by_prefix: defaultdict[str, list[dict[str, Any]]] = defaultdict(list)
for event in events:
metadata = event.get("metadata") or {}
prefix = metadata.get("prefix")
if prefix and metadata.get("event_type") == "announcement":
events_by_prefix[str(prefix)].append(event)
anomalies: list[BGPAnomaly] = []
for prefix, related_events in events_by_prefix.items():
related_collectors = _unique_collectors(related_events)
if len(related_collectors) < 2:
continue
path_signatures = Counter()
max_path_length = 0
for event in related_events:
metadata = event.get("metadata") or {}
signature = _path_signature(metadata)
if signature:
path_signatures[signature] += 1
max_path_length = max(max_path_length, len(signature))
if len(path_signatures) < 2:
continue
dominant_length = len(path_signatures.most_common(1)[0][0])
if max_path_length < max(dominant_length + 2, 5):
continue
sample_event = max(
related_events,
key=lambda event: len(_path_signature((event.get("metadata") or {}))),
)
sample_metadata = sample_event.get("metadata") or {}
sample_enrichment = sample_metadata.get("enrichment") or {}
sample_prefix_geography = sample_enrichment.get("prefix_geography") or {}
peer_scope = related_collectors
path_lengths = sorted({len(signature) for signature in path_signatures if signature})
anomalies.append(
BGPAnomaly(
snapshot_id=snapshot_id,
task_id=task_id,
source=source,
anomaly_type="route_leak_candidate",
severity="high" if max_path_length >= dominant_length + 3 else "medium",
status="active",
entity_key=f"route_leak_candidate:{prefix}:{max_path_length}:{len(related_collectors)}",
prefix=prefix,
origin_asn=sample_metadata.get("origin_asn"),
new_origin_asn=None,
peer_scope=peer_scope,
started_at=datetime.now(UTC),
confidence=min(0.58 + (0.05 * min(len(related_collectors), 4)) + (0.03 * min(max_path_length - dominant_length, 4)), 0.88),
summary=(
f"Prefix {prefix} shows divergent long AS paths across "
f"{len(related_collectors)} collectors, suggesting a possible route leak."
),
evidence={
"path_lengths": path_lengths,
"dominant_path_length": dominant_length,
"max_path_length": max_path_length,
"path_signatures": [
{"path": list(signature), "count": count}
for signature, count in path_signatures.most_common(5)
],
"events": [(item.get("metadata") or {}) for item in related_events[:10]],
"origin_asn_profile": sample_enrichment.get("origin_asn_profile"),
"rpki_validation": sample_enrichment.get("rpki_validation"),
"prefix_geography": sample_prefix_geography,
"prefix_scope": sample_enrichment.get("prefix_scope"),
"impacted_regions": sample_prefix_geography.get("regions")
or _iter_event_regions(related_events)
or sample_enrichment.get("prefix_scope", {}).get("regions", []),
},
)
)
return anomalies
def detect_path_flap_anomalies(
*,
source: str,
snapshot_id: int | None,
task_id: int | None,
events: list[dict[str, Any]],
) -> list[BGPAnomaly]:
events_by_prefix: defaultdict[str, list[dict[str, Any]]] = defaultdict(list)
for event in events:
metadata = event.get("metadata") or {}
prefix = metadata.get("prefix")
if prefix:
events_by_prefix[str(prefix)].append(event)
anomalies: list[BGPAnomaly] = []
for prefix, related_events in events_by_prefix.items():
ordered = sorted(
related_events,
key=lambda event: str((event.get("metadata") or {}).get("timestamp") or ""),
)
event_types = [str((item.get("metadata") or {}).get("event_type") or "") for item in ordered]
transitions = sum(1 for index in range(1, len(event_types)) if event_types[index] != event_types[index - 1])
distinct_paths = {
_path_signature(item.get("metadata") or {})
for item in ordered
if _path_signature(item.get("metadata") or {})
}
related_collectors = _unique_collectors(ordered)
if transitions < 3 and len(distinct_paths) < 3:
continue
sample_metadata = (ordered[0].get("metadata") or {}) if ordered else {}
sample_enrichment = sample_metadata.get("enrichment") or {}
sample_prefix_geography = sample_enrichment.get("prefix_geography") or {}
severity = "medium"
if transitions >= 5 or len(distinct_paths) >= 4:
severity = "high"
anomalies.append(
BGPAnomaly(
snapshot_id=snapshot_id,
task_id=task_id,
source=source,
anomaly_type="path_flap",
severity=severity,
status="active",
entity_key=f"path_flap:{prefix}:{transitions}:{len(distinct_paths)}",
prefix=prefix,
origin_asn=sample_metadata.get("origin_asn"),
new_origin_asn=None,
peer_scope=related_collectors,
started_at=datetime.now(UTC),
confidence=min(0.54 + (0.05 * min(transitions, 5)) + (0.03 * min(len(distinct_paths), 4)), 0.9),
summary=(
f"Prefix {prefix} shows repeated state/path changes "
f"({transitions} transitions, {len(distinct_paths)} distinct paths) in the current window."
),
evidence={
"transitions": transitions,
"event_types": event_types[:12],
"distinct_paths": [list(path) for path in list(distinct_paths)[:6]],
"events": [(item.get("metadata") or {}) for item in ordered[:10]],
"origin_asn_profile": sample_enrichment.get("origin_asn_profile"),
"rpki_validation": sample_enrichment.get("rpki_validation"),
"prefix_geography": sample_prefix_geography,
"prefix_scope": sample_enrichment.get("prefix_scope"),
"impacted_regions": sample_prefix_geography.get("regions")
or _iter_event_regions(ordered)
or sample_enrichment.get("prefix_scope", {}).get("regions", []),
},
)