Technical writing
BGP routing signals and internet shutdown detection: how Voidly uses IODA data
HTTP blocking and DNS tampering are the most visible censorship signals, but internet shutdowns — the kind that cut an entire country or province off the global network — show up first in BGP routing tables, before any probe can even send a packet. By the time an HTTP request times out, the route to that network has already been withdrawn from every major route collector in the world. Voidly watches those withdrawals in real time, and this post explains how.
What BGP tells us about connectivity
BGP — the Border Gateway Protocol — is the mechanism by which Autonomous Systems advertise reachable IP prefixes to each other. An AS advertising a prefix is making a claim: “I can route traffic to these IPs.” Every major transit provider, IXP, and regional ISP participates in this constant exchange of routing information, and the global routing table is the aggregate result of all those advertisements.
When a government orders a complete internet shutdown, the operational reality is that upstream transit providers withdraw the affected AS's routes — they stop telling the rest of the internet how to reach those IP blocks. The AS's prefixes disappear from the global routing table within minutes. No DNS manipulation, no packet filtering, no application-layer blocking: the traffic simply has nowhere to go.
We pull BGP data from IODA (Internet Outage Detection and Analysis) at Georgia Tech, which aggregates BGP feeds from three sources: the RIPE NCC Route Information Service (RIS), the University of Oregon RouteViews project, and real-time BGP alerts via the bgp.tools streaming API. Between those three, we have visibility into withdrawals from hundreds of route collectors globally, with sub-minute latency on most events. A prefix withdrawal observed at rrc00.ripe.net in Amsterdam and simultaneously at route-views.oregon-ix.net in Eugene is not a local routing anomaly — it is a global signal that the prefix has been removed from the internet's reachability graph.
A BGP withdrawal message looks like this in its simplified form:
BGP UPDATE — WITHDRAWN
withdrawn_routes:
- 185.10.48.0/21 # AS60280 (Belarusian ISP)
- 185.10.56.0/21
path_attributes: []
timestamp: 2025-01-14T19:32:11Z
observed_at: rrc00.ripe.net, route-views.oregon-ix.netThe absence of path_attributes and the presence of the prefix in withdrawn_routes with no corresponding ANNOUNCE message is the routing equivalent of going dark.
Distinguishing a shutdown from normal routing changes
Not every prefix withdrawal is a shutdown. CDN route optimization, peering agreement changes, and routine maintenance cause constant BGP churn — the global routing table sees tens of thousands of updates per minute during normal operation. A single prefix withdrawal, or even a handful from one AS, is not a signal worth surfacing.
What Voidly looks for is a cluster of correlated withdrawals: multiple prefixes from the same AS, or from multiple ASes within the same country, disappearing within a short time window. The key signal is the ratio of withdrawn prefixes to the historically stable prefix count for that country. To compute that ratio, we maintain a 90-day rolling median of active prefixes per country, sampled at 15-minute intervals. When the observed active count drops sharply below that baseline, the BGP outage score rises toward 1.0.
def bgp_outage_score(country: str, window_start: datetime, window_end: datetime) -> float:
"""
Returns 0.0–1.0 outage severity for a country in the given time window.
Uses IODA prefix-reachability data normalized against 90-day baseline.
"""
active_prefixes = query_ioda_prefix_count(country, window_start, window_end)
baseline = BASELINE_MEDIANS[country] # 90-day rolling median
if baseline == 0:
return 0.0
ratio_reachable = active_prefixes / baseline
# Invert: 1.0 = full outage, 0.0 = normal
return max(0.0, min(1.0, 1.0 - ratio_reachable))A score of 0.85 means roughly 85% of the country's normally-advertised prefixes have disappeared from the global routing table. That is not a peering change. That is a shutdown.
BGP silence vs. BGP withdrawal
There is a distinction that matters enormously in practice but is easy to miss: BGP withdrawal (an AS actively announces route removal via an UPDATE message) versus BGP silence (no updates at all, a prefix simply ages out of the routing table after the hold timer expires). These are mechanically different events with very different base rates across countries.
Silence is far more common in less-developed network infrastructure. Small ISPs in low-income countries often have flaky BGP sessions — sessions that drop due to power failures, hardware faults, or misconfiguration — without ever sending an explicit withdrawal. If we treated silence as equivalent to a government-ordered shutdown signal, we would flood alerts for routine infrastructure issues in countries like Eritrea, South Sudan, or rural Papua New Guinea, where poor BGP session stability is the baseline, not the exception.
Our classifier separates three cases: (1) explicit WITHDRAWN messages — high confidence, treated as an actionable signal immediately; (2) prefix aged out after 24 hours of no updates — medium confidence, held for corroboration before surfacing; (3) no IODA data at all for a country — excluded from the score entirely, treated as a data gap rather than an event. The third case is important: data absence is not evidence of a shutdown. IODA coverage is incomplete for some countries, and the absence of telemetry should never drive an alert on its own.
IODA's active probing via the UCSD network telescope adds a complementary signal that supplements BGP for exactly this reason. The UCSD telescope receives unsolicited traffic from across the internet (from scanning, misconfigured hosts, backscatter). When connectivity to a country is cut, that traffic drops — a country that normally produces thousands of telescope packets per hour goes quiet. This signal is independent of BGP session state, which means it catches both the explicit withdrawal case and the silence case without conflating infrastructure failures with government actions.
How BGP fits into Voidly's composite score
The BGP/IODA signal carries the highest independence weight relative to Voidly's own HTTP, DNS, and TLS measurements. In the corroboration matrix, the (voidly, ioda) pair has an independence weight of 0.95 — the highest of any source pair we track. The reason is structural: BGP operates at the network layer, uses different infrastructure (route collectors, not probes), measures different phenomena (reachability announcements, not application responses), and is collected from entirely different vantage points. A systematic error that causes Voidly's probes to mis-classify a CDN response as blocking will not affect what RIPE NCC RIS sees in the global routing table.
BGP alone is insufficient for application-level blocking — and this is the critical caveat. The Great Firewall of China does not withdraw routes. It drops packets. From a BGP perspective, Chinese networks look fully reachable, even while thousands of domains are inaccessible via packet inspection and DNS poisoning. For country-level shutdowns, BGP plus HTTP blocking together reach approximately 97% confidence that the event is real. For application-layer blocking in countries that do not disconnect at the routing level, BGP contributes almost nothing, and the composite score reflects that asymmetry.
IODA_WEIGHT = 0.30 # BGP reachability signal
VOIDLY_WEIGHT = 0.35 # Own probe signal (DNS + TLS + HTTP combined)
OONI_WEIGHT = 0.20 # OONI corroboration
CENSOREDPLANET_WEIGHT = 0.15 # CensoredPlanet corroboration
def composite_score(signals: dict[str, float]) -> float:
return (
signals.get('ioda', 0.0) * IODA_WEIGHT +
signals.get('voidly', 0.0) * VOIDLY_WEIGHT +
signals.get('ooni', 0.0) * OONI_WEIGHT +
signals.get('censoredplanet', 0.0) * CENSOREDPLANET_WEIGHT
)The weights are not fixed globally — per-country calibration applies abaseline_weight multiplier to the IODA score before it enters the composite. Countries where BGP is a reliable shutdown indicator get a multiplier near 1.0. Countries where it is structurally unreliable get a lower multiplier, downweighting the signal before it can distort the composite.
Country-specific BGP quirks
BGP reliability as a shutdown signal varies enormously by country, and the reasons are instructive for understanding how governments actually operate their internet infrastructure.
Iran is the cleanest case we work with. AS12880 (ICT Co.) controls essentially all international transit — there is a single chokepoint for international routing, and withdrawals from that AS are unambiguous. Our false-positive rate for Iran BGP signals is under 2%, which is low enough that an Iran BGP event alone can push a measurement into Corroborated tier. The centralized topology makes attribution straightforward and the signal reliable.
China is the opposite extreme. BGP is stable; China does not do country-level prefix withdrawals for censorship purposes. The Great Firewall operates entirely at the packet-inspection and DNS layer, leaving the routing table intact. BGP is effectively a null signal for China — we downweight it to near zero for application-level blocking events there, and we would only expect it to matter if China ever conducted a full international disconnection, which has not occurred. Relying on BGP to detect Great Firewall blocking would produce systematic false negatives.
Russia presents a more complex picture. The network is highly federated — Rostelecom, MTS, Beeline, MegaFon, and dozens of regional ISPs all have diverse upstream transit paths. A Rostelecom route withdrawal may have no effect on what a MTS subscriber can reach. We track per-AS coverage fractions for Russia, and a BGP signal only enters the composite score when the withdrawn prefixes represent a significant fraction of the country's total advertised space, not just one carrier's allocation.
Belarus presents the most operationally interesting case. After the 2020 post-election protests, the government ordered major ISPs to maintain their BGP sessions — deliberately keeping routes advertised — while blocking traffic at the packet level to avoid triggering BGP-based detection. Pure BGP monitoring would have missed most of what happened during that period. IODA's active probing and telescope data, combined with Voidly's application-layer probes, caught the event where BGP would not. Belarus is why we weight BGP as one signal among several rather than a primary detector — a sufficiently sophisticated government can evade BGP-based monitoring entirely while conducting a near-complete shutdown at layer 3.
For how Voidly's probe generates the HTTP, DNS, and TLS measurements that complement BGP signals: How Voidly's probe generates HTTP, DNS, and TLS measurements →
For how signals across OONI, CensoredPlanet, and IODA are aligned and reconciled in the same time window: How signals across OONI, CensoredPlanet, and IODA are reconciled →
For how Voidly uses historical shutdown patterns to produce seven-day forecasts: How Voidly predicts shutdowns seven days in advance →
For the full schema of the bgp_withdrawal and bgp_outage_scorefields in the published dataset: The Voidly measurement dataset: field-by-field schema reference →
For how BGP outage scores combine with DNS/HTTP/TLS interference probabilities to produce per-country censorship rankings: Voidly's country-level censorship score: aggregating 2.2B probe measurements into the global index →
For a deeper look at how BGP data is ingested from RIPE NCC RIS, RouteViews, and bgp.tools — MRT parsing, per-country baselines, and how bgp_outage_score reaches probe measurements: Voidly BGP data ingestion: parsing MRT dumps, detecting prefix withdrawals, and computing country outage scores →
For how AS path topology data locates the specific network hop where censorship is enforced — IXP-level, transit AS, or edge ISP: Voidly AS path analysis: using BGP topology to locate censorship enforcement points →
For per-ASN blocking analysis — how censorship propagates differently across ISPs within the same country: Voidly's ASN-level blocking analysis: how censorship propagates across autonomous systems →