<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>BGPKIT Blog</title><description>Updates, releases, and technical notes from the BGPKIT team.</description><link>https://bgpkit.com/</link><language>en-us</language><atom:link href="https://bgpkit.com/blog/rss.xml" rel="self" type="application/rss+xml"/><item><title>Anatomy of the Virtualizor BGP Hijack: Reading the Attack in Raw BGP Bytes</title><link>https://bgpkit.com/blog/virtualizor-bgp-hijack-anatomy/</link><guid isPermaLink="true">https://bgpkit.com/blog/virtualizor-bgp-hijack-anatomy/</guid><description>How AS62390 hijacked Hetzner&apos;s 162.55.80.0/24 to poison Virtualizor&apos;s update pipeline, reconstructed message-by-message from public MRT data, and why the hijacked route was RPKI-valid by design.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On August 31, 2026, Softaculous disclosed that a BGP hijack against their
infrastructure two days earlier was not just a routing outage: during the
diversion, a malicious Virtualizor update package was served to hosting
providers, compromising hypervisors at the root level
&lt;a href=&quot;https://www.virtualizor.com/blog/security-incident-bgp-hijacking/&quot;&gt;[1]&lt;/a&gt;.
This post reconstructs the routing attack from primary data, the BGP
UPDATE messages recorded by public route collectors, and follows the
failure chain from RPKI origin validation, through certificate
issuance, to an unsigned software-update channel.&lt;/p&gt;
&lt;p&gt;Everything below is reproducible from public archives: RIPE RIS and Route
Views store every message, and the tools we use to slice them
(&lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt; and
&lt;a href=&quot;https://bgpkit.com/tools/broker/&quot;&gt;BGPKIT Broker&lt;/a&gt;) are open source. A PDF version
of this analysis is available:
&lt;a href=&quot;https://reports.bgpkit.com/2026/IR-2026-0831/bgpkit-incident-report-IR-2026-0831.pdf&quot;&gt;Full incident report (PDF)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The one-paragraph summary&lt;/h2&gt;
&lt;p&gt;Between 2026-08-28 20:57:30 UTC and 2026-08-30 ~06:10 UTC, AS62390
(NexonHost) announced &lt;code&gt;162.55.80.0/24&lt;/code&gt;, a slice of Hetzner&apos;s
&lt;code&gt;162.55.0.0/16&lt;/code&gt; that hosts Softaculous&apos; update and billing servers,
through transit provider AS6204 (ZET.NET). The forged announcement was
&lt;em&gt;more specific&lt;/em&gt; than Hetzner&apos;s legitimate /16, so it won route selection
everywhere it reached. Crucially, the operator of AS62390 kept the real origin,
AS24940, at the end of the AS path, which made the hijacked route validate
cleanly against Hetzner&apos;s RPKI ROA. Traffic for the update servers flowed
to the impostor, who used the diversion to pass Let&apos;s Encrypt&apos;s domain
validation and mint a legitimate TLS certificate for 26 Softaculous
domains, cloned the update endpoint, and served a backdoored update to
installations that happened to check during the window.&lt;/p&gt;
&lt;h2&gt;The hijack path&lt;/h2&gt;
&lt;p&gt;The path below reads observer → origin, the direction an MRT record
stores. Every vantage that received the forged route saw some variant of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;#x3C;observer&gt; &amp;#x3C;upstream...&gt; 6204 62390 24940
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Segment by segment, from the origin end:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;24940&lt;/code&gt;: Hetzner, the real origin. The attacker kept it on the path
as camouflage, so RPKI origin validation saw the authorized origin and
passed the route.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;62390&lt;/code&gt;: NexonHost, the hijacking AS. It forged the announcement and
inserted itself one hop before the origin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;6204&lt;/code&gt;: ZET.NET, the transit provider that accepted and propagated
the forged route to the rest of the Internet.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;#x3C;upstream...&gt;&lt;/code&gt;: however many additional ASNs the route accumulated
between AS6204 and the recording collector, different at every vantage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attack topology in one picture:&lt;/p&gt;
&lt;pre class=&quot;mermaid wide&quot;&gt;graph LR
  LEG[&quot;162.55.0.0/16 | RPKI ROA: AS24940, maxlen 24&quot;]
  H[&quot;AS24940 Hetzner&lt;br&gt;legitimate /16&quot;] -- &quot;announces /16&quot; --&gt; W[&quot;The Internet&lt;br&gt;(longest prefix match)&quot;]
  N[&quot;AS62390 NexonHost&lt;br&gt;hijacking AS / apparent source&quot;] -- &quot;announces 162.55.80.0/24&lt;br&gt;AS path: ... 6204 62390 24940&quot; --&gt; Z[&quot;AS6204 ZET.NET&lt;br&gt;transit that propagated the forged route&quot;]
  Z -- &quot;more specific wins&quot; --&gt; W
  W -- &quot;diverted traffic&quot; --&gt; IMP[&quot;impostor server&lt;br&gt;at 162.55.80.8&quot;]
  style N fill:#fee2e2,stroke:#dc2626
  style Z fill:#ffedd5,stroke:#ea580c
  style IMP fill:#fee2e2,stroke:#dc2626
  style H fill:#dcfce7,stroke:#16a34a
&lt;/pre&gt;
&lt;p&gt;Two properties did all the work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;More-specific beats origin authority.&lt;/strong&gt; BGP route selection prefers
the longer prefix regardless of who announces it. Hetzner&apos;s /16 could
not compete with a /24 covering the same space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The real origin stayed on the path.&lt;/strong&gt; This is what separates this
incident from a naive origin hijack, and it is why RPKI origin
validation, deployed anywhere along the path, rejected nothing. More
on that below.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Reading the first hijacked message, byte by byte&lt;/h2&gt;
&lt;p&gt;The first collector sighting of the forged route is a single MRT record
from RIPE RIS rrc00, timestamped 2026-08-28 20:57:30 UTC. Here it is as a
BGP4MP-wrapped UPDATE:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BGP4MP|1787950650|A|185.193.84.191|29504|162.55.80.0/24|29504 15935 6204 62390 24940|IGP|...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can open this exact record, dissected field-by-field down to every
byte, in &lt;a href=&quot;https://wirescope.labs.bgpkit.com/#hex=6a91f63a0010000400000084000073400000316e00000001b9c154bfc100041cffffffffffffffffffffffffffffffff007002000000554001010040021602050000734000003e3f0000183c0000f3b60000616c400304b9c154bf80040400000032c00818183c03e8183c03ea3e3f00643e3f006779fe000079fe0002c0200c000079fe0000076c0000000118a23750&quot;&gt;wirescope&lt;/a&gt;,
our Wireshark-style BGP message dissector. Click any tree row to pin the
corresponding bytes. Three fields are worth your attention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AS_PATH&lt;/strong&gt; (&lt;code&gt;4002 16 02 05 ...&lt;/code&gt;): an AS_SEQUENCE of five ASNs,
&lt;code&gt;29504 15935 6204 62390 24940&lt;/code&gt;. The last two ASNs are the entire attack:
62390 appeared immediately before the real origin; routing data alone cannot say who operated it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NLRI&lt;/strong&gt; (&lt;code&gt;18 a2 37 50&lt;/code&gt;): prefix length 24, then 162.55.80.0: the
more-specific that wins longest-prefix match.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Communities&lt;/strong&gt;: &lt;code&gt;6204:1000&lt;/code&gt;, &lt;code&gt;6204:1002&lt;/code&gt; stamped inside AS6204,
the transit that carried the forged route, &lt;code&gt;15935:100 15935:103&lt;/code&gt; and &lt;code&gt;31230:0 31230:2&lt;/code&gt; picked up along the
way. Communities accumulate hop by hop, which is how a third party can
later reconstruct which path the route took.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We extracted those 144 bytes verbatim with
&lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt;&apos;s &lt;code&gt;RawMrtRecord::raw_bytes()&lt;/code&gt; API, so what you see in wirescope is the
unaltered wire image from the collector, not a reconstruction.&lt;/p&gt;
&lt;h2&gt;Timeline, verified against raw MRT where possible&lt;/h2&gt;
&lt;p&gt;Virtualizor&apos;s disclosure includes a minute-by-minute table reconstructed
from RIPEstat. We re-verified the key moments directly against raw update
files from two independent collector projects; that is the level of
evidence routing claims deserve. Every timestamp below is from the
primary data. Throughout the post we separate three evidence classes:
verified directly from MRT (routes, paths, propagation, withdrawals);
verified from other public data (ROA, ASPA corpus); and reported by
Virtualizor or operators (malicious package, impostor host behavior,
malware IoCs).&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Moment (UTC)&lt;/th&gt;&lt;th&gt;What happened&lt;/th&gt;&lt;th&gt;Evidence&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;08-28 20:57:30&lt;/td&gt;&lt;td&gt;First forged /24 seen; &lt;code&gt;29504 15935 6204 62390 24940&lt;/code&gt; at rrc00, same second at rrc01 via a different transit chain (&lt;code&gt;6830 6204 62390 24940&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;RIS rrc00/rrc01 &lt;code&gt;updates.20260828.2055&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;08-28 21:01:04&lt;/td&gt;&lt;td&gt;First Route Views sighting, &lt;code&gt;57866 5511 6204 62390 24940&lt;/code&gt;&lt;/td&gt;&lt;td&gt;route-views2 &lt;code&gt;updates.20260828.2100&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;08-29 08:45-08:50&lt;/td&gt;&lt;td&gt;Hetzner countermeasures: begins announcing the /24 itself (e.g. &lt;code&gt;852 3320 3320 3320 24940&lt;/code&gt;); diversion drops to zero in minutes&lt;/td&gt;&lt;td&gt;RIS rrc00 &lt;code&gt;updates.20260829.0850&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;08-29 ~14:30&lt;/td&gt;&lt;td&gt;Hetzner withdraws the countermeasure /24; Internet falls back to the /16&lt;/td&gt;&lt;td&gt;RIPEstat bgp-state table &lt;a href=&quot;https://www.virtualizor.com/blog/security-incident-bgp-hijacking/&quot;&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;08-29 19:55:25&lt;/td&gt;&lt;td&gt;Second wave begins, same path signature, minutes after the clean /24 disappeared&lt;/td&gt;&lt;td&gt;RIS rrc00 &lt;code&gt;updates.20260829.1955&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;08-30 05:45:16&lt;/td&gt;&lt;td&gt;Last forged-route record at rrc00; zero 62390 sightings after 05:50&lt;/td&gt;&lt;td&gt;RIS rrc00 &lt;code&gt;updates.20260830.0545&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The two-wave structure is worth pausing on. The forged route
disappeared as Hetzner&apos;s countermeasure took effect, then reappeared
several hours after the clean /24 was withdrawn. The routing data alone
cannot establish whether that timing was deliberate, but the second
wave reopened the same diversion path.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Phase&lt;/th&gt;&lt;th&gt;Window (UTC)&lt;/th&gt;&lt;th&gt;Duration&lt;/th&gt;&lt;th&gt;Diversion&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Wave 1&lt;/td&gt;&lt;td&gt;08-28 20:57 → 08-29 08:50&lt;/td&gt;&lt;td&gt;~12 h&lt;/td&gt;&lt;td&gt;high, flapping (peak ~72% of RIS peers)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Suppressed&lt;/td&gt;&lt;td&gt;08-29 08:50 → 19:55&lt;/td&gt;&lt;td&gt;~11 h&lt;/td&gt;&lt;td&gt;~zero (clean /24, then /16 fallback)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Wave 2&lt;/td&gt;&lt;td&gt;08-29 19:55 → 08-30 05:45&lt;/td&gt;&lt;td&gt;~10 h&lt;/td&gt;&lt;td&gt;high again, same path signature&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Through the waves, the route was exceptionally unstable: roughly 10,600
withdrawals inside the ~33-hour window (per the disclosure&apos;s event
reconstruction), with transit route dampening repeatedly suppressing it.
Peak diversion reached ~72% of RIS&apos;s 368 collector peers (median 266),
and every single RIS peer carried the forged route at some point. Those
percentages are routing-visibility measures, not traffic volume; the
flapping means any individual network&apos;s exposure was intermittent.&lt;/p&gt;
&lt;h3&gt;How long the hijack &quot;worked&quot; end to end&lt;/h3&gt;
&lt;p&gt;The question that matters for a supply-chain attack is not when routes
flapped but how long the impostor could actually serve forged content
with a valid certificate. Summing only the phases where the forged route
was propagated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aggregate attack-wave duration: ~22 of the 33 hours&lt;/strong&gt;, adding wave 1
(about 12 hours) and wave 2 (about 10 hours). Actual exposure at any
individual network was intermittent because of the severe flapping and
dampening quantified below.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROV exposure: the entire window.&lt;/strong&gt; No RPKI mechanism would have
rejected the route; it validated as &lt;em&gt;Valid&lt;/em&gt; from the first announce to
the last withdrawal. The only effective countermeasure was Hetzner
answering the forged /24 with its own legitimate /24, and it took &lt;strong&gt;~12 hours
from onset to take effect&lt;/strong&gt; (08-28 20:57 → 08-29 08:50), reportedly
after escalation by the victim rather than proactive detection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certificate validity outlived the hijack.&lt;/strong&gt; The fraudulently issued
certificate was technically valid the moment Let&apos;s Encrypt&apos;s domain
validation completed over the diverted path. Once issued, it kept
working for any on-path position the impostor could regain, which is
exactly what wave 2 was. Revocation of the misissued cert is the only
thing that truly retires it; the disclosure does not state a revocation
time, so treat the certificate as live through at least the end of the
incident window (08-30 06:10), a &lt;strong&gt;~31-hour worst case from onset&lt;/strong&gt;;
the attack waves themselves span ~22 of those hours, with intermittent
exposure as quantified above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The gap between &quot;route visible&quot; and &quot;operator response&quot; is the real
story: public collectors saw the forged route at 20:57:30, the effective
countermeasure arrived roughly 12 hours later, and throughout that
interval the forged announcement remained RPKI Valid: ROV provided no
basis for rejecting it.&lt;/p&gt;
&lt;h2&gt;What the entire public collector fleet saw&lt;/h2&gt;
&lt;p&gt;The disclosure&apos;s diversion numbers cover RIPE RIS&apos;s 368 peers. We wanted
the whole public picture, so we streamed every update file from both
public collector projects (RIPE RIS and Route Views, 79 collectors,
22,613 files, 94,117 route events) through a Rust replay built on
&lt;a href=&quot;https://bgpkit.com/tools/broker/&quot;&gt;bgpkit-broker&lt;/a&gt; and
&lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt;, classifying every collector peer&apos;s best
route for the /24 minute by minute: hijacked (path through AS62390) or
on Hetzner&apos;s clean /24. The result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bgpkit.com/blog-assets/virtualizor-bgp-hijack-anatomy/hijack-visibility.png&quot; alt=&quot;Hijack visibility across all public collector peers&quot;&gt;&lt;/p&gt;
&lt;p&gt;Reading the curve: at onset the forged route reaches &lt;strong&gt;584 peers within
its first minute&lt;/strong&gt; and peaks at &lt;strong&gt;615 peers&lt;/strong&gt; simultaneously diverted
(about three hours into wave 1). When Hetzner&apos;s countermeasure /24 appears at 08:50, the
mix flips almost instantly to green (clean /24 held by 787+ peers), and
the forged route stays near-zero through the quiet window. Wave 2 snaps
~609 peers back onto the hijacked path within minutes of the clean /24
disappearing, and the final withdrawal at 05:45 collapses it in one
step. One detail the aggregate view exposes that per-collector checks
miss: a single collector peer still held the hijacked route for about an
hour after everything else had converged (the thin red tail past 05:45),
a classic BGP zombie. Raw minute-bucket data:
&lt;a href=&quot;https://bgpkit.com/blog-assets/virtualizor-bgp-hijack-anatomy/hijack-visibility-buckets.tsv&quot;&gt;hijack-visibility-buckets.tsv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The gaps in the curve are real routing states, not missing data. In
wave 1 the forged route flapped so violently that transit route dampening
repeatedly suppressed it to a single peer for hours at a stretch (the
disclosure&apos;s table shows the same near-zero rows). And the blue-shaded
mid-day window is genuinely empty: after Hetzner withdrew its
countermeasure /24 around 14:30 and before the impostor returned at
19:55, no /24 route existed anywhere in the public view; every network
had fallen back to the legitimate /16. Minute-by-minute visibility
therefore swung between &quot;hijack everywhere&quot;, &quot;suppressed to almost
nothing&quot;, and &quot;no /24 at all&quot;; an operator watching only reachability
of the /24 could reasonably have believed the attack was over three
separate times.&lt;/p&gt;
&lt;h3&gt;Where the flapping came from&lt;/h3&gt;
&lt;p&gt;We also traced the flapping itself. For every wave-1 announcement we
extracted the AS immediately upstream of AS6204 (the carrier that
relayed the forged route into the transit) and measured the
announce/withdraw rhythm per collector peer. Two facts pin the churn to
the source rather than to any transit re-computing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;All carriers cycled together.&lt;/strong&gt; Sixteen distinct upstream ASes
relayed the route into AS6204 (AS5511 with 2,370 observations, AS3257
with 1,065, plus fourteen more). Every one of them shows the same
announce/withdraw cycling. The synchronized churn across otherwise
independent carrier paths localizes the instability to the common
segment at or below AS6204, most plausibly AS62390&apos;s export or the
AS62390-AS6204 session, rather than to independent recomputation
farther upstream.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Peers around the world alternate almost one-to-one.&lt;/strong&gt; Collectors in Singapore, Korea, Brazil, South Africa, the UAE, and the US all saw
announce/withdraw ratios near one, a complete up/down cycle roughly
every nine minutes on average. That pattern is what a single source switching on and off, with every
network faithfully repeating it, looks like; independent local routing
decisions would not stay in sync.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The mechanism, end to end: the source export (on the AS62390-AS6204
segment) cycled, each drop triggered
a withdrawal avalanche (the largest was 49 withdrawals in a single
second, 75 seconds after onset), each recovery re-propagated the route,
and the accumulated flapping pushed route-dampening networks along the
path into suppressing it for 30-60 minutes at a stretch. Sloppy
infrastructure or deliberate on/off hijacking: routing data cannot
distinguish intent, but the synchronized churn localizes the
instability to the AS62390-AS6204 segment, and the intermittent result
was still enough to capture update checks and a certificate
validation.&lt;/p&gt;
&lt;p&gt;The counts here are collector-peer route states (all RIS + Route Views
peers that carried a /24 route), a broader denominator than the
disclosure&apos;s 368 RIS peers, so absolute numbers differ; the shape and
the instants match the verified timeline exactly.&lt;/p&gt;
&lt;h2&gt;Why RPKI didn&apos;t stop it&lt;/h2&gt;
&lt;p&gt;The attacker did not forge the origin; the forged information was the
relationship immediately before the origin. A ROA answers &quot;is AS24940
allowed to originate this prefix?&quot; and the answer was genuinely yes. The
attack hinged on a different question: &quot;is AS62390 allowed to claim
connectivity to AS24940 this way?&quot; Origin validation has no way to ask
it. Everything in this section follows from that distinction.&lt;/p&gt;
&lt;p&gt;Hetzner&apos;s ROA for the covering block is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;162.55.0.0/16 | origin AS24940 | maxLength 24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Verifiable at any RPKI viewer; we confirmed the ROA held continuously
through the incident window via BGPKIT&apos;s ROA history service, which
stores daily snapshots back to 2011.)&lt;/p&gt;
&lt;p&gt;Now validate the hijacked route the way a ROV-enforcing router would:&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;flowchart TD
  A[&quot;Route: 162.55.80.0/24&lt;br&gt;AS path ... 6204 62390 24940&quot;] --&gt; B{&quot;Covering ROA?&quot;}
  B -- &quot;yes: 162.55.0.0/16&quot; --&gt; C{&quot;Origin AS in ROA?&lt;br&gt;announced origin = 24940&quot; }
  C -- &quot;yes&quot; --&gt; D{&quot;Prefix length ≤ maxLength?&lt;br&gt;24 ≤ 24&quot; }
  D -- &quot;yes&quot; --&gt; E[&quot;RPKI: VALID ✅&lt;br&gt;route accepted, hijack invisible to ROV&quot;]
  style E fill:#fef9c3,stroke:#ca8a04
&lt;/pre&gt;
&lt;p&gt;Because the forged path &lt;em&gt;appended&lt;/em&gt; the real origin instead of replacing it,
the announcement&apos;s origin ASN is exactly what the ROA authorizes, at
exactly the maximum allowed length. ROV classified the forged route as Valid;
even universal ROV deployment, by itself, would not have rejected this
announcement.&lt;/p&gt;
&lt;p&gt;A further wrinkle: had Hetzner signed the ROA for exactly the /16 without
&lt;code&gt;maxLength 24&lt;/code&gt; (the minimal-ROA practice recommended by
&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9319&quot;&gt;RFC 9319&lt;/a&gt;), the hijacked
/24 would have been RPKI &lt;em&gt;Invalid&lt;/em&gt; and dropped by every validating
network; the over-broad &lt;code&gt;maxLength&lt;/code&gt; is what kept it Valid. Bryton Herdes
made exactly this point when the disclosure
&lt;a href=&quot;https://x.com/next_hopself/status/2094553871087509799&quot;&gt;hit the community&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This forged-origin pattern
(real origin retained, unauthorized AS inserted before it) is exactly
what &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9319&quot;&gt;RFC 9319&lt;/a&gt; warns
about (see also the
BGPKIT use-case note on &lt;a href=&quot;https://bgpkit.com/blog/aspa-path-verification-explained/&quot;&gt;ASPA path verification&lt;/a&gt;),
and what ASPA
(&lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/&quot;&gt;draft-ietf-sidrops-aspa-verification&lt;/a&gt;)
is designed to catch.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;would&lt;/em&gt; have stopped the route from propagating? Unexciting,
decades-old prefix filtering at the transit: a customer-prefix filter at
AS6204 that restricted AS62390 to prefixes it was authorized to originate
would have rejected this announcement. Its acceptance and re-export
turned a single suspicious announcement into a global diversion. Customer
prefix filtering at transit remains the control that would have confined
this incident to a single hop.&lt;/p&gt;
&lt;h3&gt;How ASPA would have changed the outcome&lt;/h3&gt;
&lt;p&gt;ROV authorizes &lt;em&gt;origins&lt;/em&gt;; it says nothing about &lt;em&gt;who may sit immediately
upstream of the origin&lt;/em&gt;, which is exactly the degree of freedom this attack used.
ASPA closes that gap, and its effect here is unusually concrete, so we
replayed the incident&apos;s actual paths through the ASPA verification
algorithm (draft §5.2-5.6) under three ASPA states. The &quot;today&quot; state is
not hypothetical: we checked the live global ASPA corpus (rpki-client
export, 2,820 objects): no AS on these paths has published one, and no
object anywhere lists AS62390 as a provider.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ASPA state&lt;/th&gt;&lt;th&gt;Hijack /24&lt;/th&gt;&lt;th&gt;Legit /16&lt;/th&gt;&lt;th&gt;Hetzner countermeasure /24&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Today (no ASPA on path)&lt;/td&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ASPA(24940) published&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Invalid&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;td&gt;Unknown / Valid†&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ASPA(24940) + ASPA(6204)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Invalid&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Unknown&lt;/td&gt;&lt;td&gt;Unknown / Valid†&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;† upstream (customer/peer-received) / downstream (provider-received)
verdict; the recommended mitigation policy rejects Invalid only.&lt;/p&gt;
&lt;p&gt;Why one object suffices: Hetzner&apos;s ASPA lists its &lt;em&gt;real&lt;/em&gt; providers, which
by definition exclude AS62390, so the hop &lt;code&gt;24940 → 62390&lt;/code&gt; becomes
provably &quot;Not Provider+&quot;. The up-ramp from the origin collapses to zero
hops and the algorithm halts at Invalid, in both the upstream and
downstream variants. No cooperation from AS62390, AS6204, or
anyone else on the path is needed.&lt;/p&gt;
&lt;p&gt;Why there is no collateral damage: Hetzner&apos;s legitimate routes stay
&lt;em&gt;Unknown&lt;/em&gt; (not Invalid) because their un-attested hops are &quot;No
Attestation&quot; (a missing assertion) rather than a &lt;em&gt;contradicted&lt;/em&gt; one.
Under the reject-Invalid-only policy, publishing the ASPA costs Hetzner
nothing and takes the entire forged-upstream attack class off the table
for its space.&lt;/p&gt;
&lt;p&gt;You can watch this counterfactual play out live: Cloudflare Radar&apos;s
&lt;a href=&quot;https://radar.cloudflare.com/routing/aspa-validation?path=29504-15935-6204-62390-24940&quot;&gt;ASPA validation tool&lt;/a&gt;
checks any AS path against the ASPA records currently published, hop by
hop. Paste the hijack path (or follow
&lt;a href=&quot;https://radar.cloudflare.com/routing/aspa-validation?path=29504-15935-6204-62390-24940&quot;&gt;this pre-filled link&lt;/a&gt;)
and the verdict today is Unknown. The day Hetzner publishes its ASPA,
the same link turns Invalid for everyone who opens it.&lt;/p&gt;
&lt;p&gt;And the deployment asymmetry is the punchline: defenders opt in one
object at a time, each hardening their own origin regardless of what
anyone else does. Had ASPA(24940) existed and verification been enabled
at even the major transits that carried the forged route (AS3257,
AS5511, AS6830, AS3320 were all on observed paths), the 20:57:30
announcement could have been classified Invalid and filtered at every
validating network, without requiring AS62390 or AS6204 to publish
ASPA of their own: no sustained diversion, no certificate minted
through the hijacked path, no poisoned update channel.&lt;/p&gt;
&lt;h2&gt;Why TLS didn&apos;t stop it&lt;/h2&gt;
&lt;p&gt;With traffic for &lt;code&gt;files.virtualizor.com&lt;/code&gt; (162.55.80.8 during the
incident) flowing to the impostor, the certificate layer was subverted
in a familiar way: the impostor requested certificates from Let&apos;s Encrypt, and
the CA&apos;s automated domain-control validation, an HTTP request to the
hijacked address, completed over the diverted path. The result was a
technically valid cert covering &lt;code&gt;virtualizor.com&lt;/code&gt;,
&lt;code&gt;api.virtualizor.com&lt;/code&gt;, &lt;code&gt;files.virtualizor.com&lt;/code&gt;, &lt;code&gt;softaculous.com&lt;/code&gt;, and 22
more names (full list in the disclosure&apos;s Appendix A &lt;a href=&quot;https://www.virtualizor.com/blog/security-incident-bgp-hijacking/&quot;&gt;[1]&lt;/a&gt;).
No client saw a warning. The impostor host even carried reverse DNS
&lt;code&gt;server.softaculous.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the update channel itself, the final blow was that Virtualizor&apos;s
update client did not cryptographically verify package signatures: a
valid-looking HTTPS response from a lookalike server was sufficient to
deliver the payload. The fake update advertised itself as 3.2.9.8;
affected hosts reported the &quot;update succeeded&quot; while remaining on
3.2.9.7; the version mismatch in the logs is itself a detectable IoC
&lt;a href=&quot;https://lowendtalk.com/discussion/220625/urgent-virtualizor-compromised-31st-aug/p1&quot;&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The full chain, then:&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;flowchart LR
  BGP[&quot;BGP hijack&lt;br&gt;RPKI-valid /24&quot;] --&gt; CERT[&quot;Let&apos;s Encrypt DCV&lt;br&gt;via hijacked path&quot;]
  CERT --&gt; TLS[&quot;valid cert ×26 domains&lt;br&gt;no client warnings&quot;]
  BGP --&gt; CLONE[&quot;clone of update server&lt;br&gt;at 162.55.80.8&quot;]
  TLS --&gt; PKG[&quot;backdoored update pkg&lt;br&gt;(unsigned client accepts it)&quot;]
  CLONE --&gt; PKG
  PKG --&gt; ROOT[&quot;hypervisor root&lt;br&gt;systemd persistence + SSH key&quot;]
  style BGP fill:#fee2e2,stroke:#dc2626
  style ROOT fill:#fee2e2,stroke:#dc2626
&lt;/pre&gt;
&lt;p&gt;Known indicators from the merged YARA rules
&lt;a href=&quot;https://github.com/Neo23x0/signature-base/pull/417&quot;&gt;[2]&lt;/a&gt;:
a &lt;code&gt;java-jre-update.service&lt;/code&gt; systemd unit, a &lt;code&gt;/tmp/widdow.jar&lt;/code&gt;-class Java
payload (SHA-256 &lt;code&gt;b81a4e1f...a7870c7&lt;/code&gt;), a planted ed25519 SSH key, and
C2 endpoints including &lt;code&gt;31.77.220.138:2025&lt;/code&gt;. Virtualizor could not
produce a victim list (malicious responses never touched their logs), so
every installation that ran an update check inside the window must be
treated as potentially affected.&lt;/p&gt;
&lt;h2&gt;Detecting this class of hijack with BGPKIT tooling&lt;/h2&gt;
&lt;p&gt;The forged route was visible in public BGP data at 20:57:30 on the
second it was first announced. The supply-chain consequences were only
community-confirmed 2.5 days later. The information was there; the
&lt;em&gt;workflow&lt;/em&gt; was missing. So here is the workflow, concretely, with the
tools we used on this very incident.&lt;/p&gt;
&lt;h3&gt;Stage 1: notice, a new more-specific inside a valid covering block&lt;/h3&gt;
&lt;p&gt;The detection predicate for this incident is one sentence: &lt;strong&gt;a /24 that
had never been announced before appeared inside an RPKI-valid /16, and
the AS right before the origin was one nobody had ever seen adjacent to
that origin.&lt;/strong&gt; RPKI origin validation checks out (that is the attack),
so the discriminator is the &lt;em&gt;penultimate hop&lt;/em&gt;, not the origin. We
verified the &quot;never announced before&quot; half directly: zero &lt;code&gt;162.55.80.0/24&lt;/code&gt;
records in rrc00&apos;s update file immediately preceding onset. Daily RIB
snapshots give you the baseline side for free.&lt;/p&gt;
&lt;p&gt;Continuously running that predicate is a pipeline job, not a CLI query:
discover each new update file as it lands
(&lt;a href=&quot;https://bgpkit.com/tools/broker/&quot;&gt;BGPKIT Broker&lt;/a&gt; &lt;code&gt;new_file&lt;/code&gt; event
stream), stream it through &lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt;, and diff what
you see against
a per-prefix baseline built from the previous day&apos;s RIBs. That is exactly
the architecture of our spectrum pipeline: Broker for discovery,
parser for the streaming pass, a small state machine for the
&quot;new more-specific + unseen penultimate hop&quot; rule.&lt;/p&gt;
&lt;p&gt;Once the alert fires, &lt;a href=&quot;https://bgpkit.com/tools/monocle/&quot;&gt;monocle&lt;/a&gt; is the tool for the
investigation that follows. It resolves the exact MRT files (via the Broker) and slices
them by prefix, origin, or AS path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# baseline: has this /24 ever existed? (monocle over yesterday&apos;s RIBs)
monocle search -t &amp;#x3C;yesterday&gt; -D rib -p &quot;162.55.80.0/24&quot;

# live: new more-specific appears; who is the origin-adjacent AS?
monocle search -t 1787950650 -d 60s -c rrc00 -p &quot;162.55.80.0/24&quot; \
  -a &quot;62390&quot; -f timestamp,as_path,peer_asn
# 1787950650 | 162.55.80.0/24 | 29504 15935 6204 62390 24940 | 29504
# 1787950651 | 162.55.80.0/24 | 59919 9002 3257 6204 62390 24940 | 59919
# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Stage 2: corroborate, independent vantage, multiple transit chains&lt;/h3&gt;
&lt;p&gt;One collector can be a policy artifact. Two projects, different
continents, different transit chains (RIS saw it via &lt;code&gt;15935 6204&lt;/code&gt; and
&lt;code&gt;6830 6204&lt;/code&gt;; Route Views via &lt;code&gt;5511 6204&lt;/code&gt;) make the finding robust. The
The &lt;a href=&quot;https://bgpkit.com/api#op-get-v3-broker-search&quot;&gt;BGPKIT Broker search
API&lt;/a&gt; is how each of these commands
finds its exact input file: one API call, timestamp + collector + data
type, no guessing archive URLs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# route-views2 saw the same forged origin 3.5 minutes later
curl -sO https://archive.routeviews.org/route-views2/bgpdata/2026.08/UPDATES/updates.20260828.2100.bz2
monocle parse updates.20260828.2100.bz2 -p &quot;162.55.80.0/24&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Stage 3: attribute the trust failure, ROA history&lt;/h3&gt;
&lt;p&gt;The claim &quot;RPKI-valid by design&quot; is checkable against a ROA &lt;em&gt;history&lt;/em&gt;
service, not just today&apos;s snapshot: the ROA must have been in force
&lt;em&gt;at incident time&lt;/em&gt;. The &lt;a href=&quot;https://bgpkit.com/api#op-get-v3-roas-search&quot;&gt;BGPKIT ROA history API&lt;/a&gt;
answers this with one call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl &quot;https://api.bgpkit.com/v3/roas/search?prefix=162.55.0.0/16&quot;
# -&gt; 162.55.0.0/16 | AS24940 | max_len 24 | date_ranges through 2026-08-31
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Stage 4: pin the evidence, bytes, not summaries&lt;/h3&gt;
&lt;p&gt;When an incident escalates, &quot;we saw a weird path&quot; is not evidence. The
144-byte MRT record is. The
&lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt; &lt;code&gt;RawMrtRecord&lt;/code&gt; API exists exactly
for this: parse the archive, match the record, keep the unmodified wire
bytes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;for raw in parser.into_raw_record_iter() {
    let wire = raw.raw_bytes();          // freeze the evidence
    let rec = raw.parse()?;              // then interpret it
    // match: prefix == 162.55.80.0/24 &amp;#x26;&amp;#x26; path contains 62390
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frozen bytes feed the wirescope deep link above, so anyone can audit
the claim against the raw message, no BGPKIT infrastructure required.&lt;/p&gt;
&lt;h3&gt;Stage 5: report it&lt;/h3&gt;
&lt;p&gt;The difference between a claim and evidence is a source list: which MRT
file, which timestamp, which exact bytes. Every command in stages 1-4 is
cheap to re-run; write them down as the report&apos;s replay recipe, and
anyone can audit the conclusions against public archives.&lt;/p&gt;
&lt;p&gt;The uncomfortable summary of this incident for the &quot;just deploy RPKI&quot;
era: origin validation is necessary, but it could not detect this attack. The hijack
lived in the gap RPKI explicitly does not cover, namely &lt;em&gt;who is allowed
to be your upstream&lt;/em&gt;. That is the gap ASPA targets, while AS62390&apos;s
own transit accepting an out-of-scope prefix is the gap filtering
discipline targets. And once routing folds, only update-client package
signing separates a routing incident from a supply-chain compromise. In
this incident it was absent, so nothing stood at the last line of
defense.&lt;/p&gt;
&lt;h2&gt;Reproducing this analysis&lt;/h2&gt;
&lt;p&gt;Every number and timestamp above comes from public data you can pull
yourself:&lt;/p&gt;
&lt;p&gt;Locate the exact MRT files with the
&lt;a href=&quot;https://bgpkit.com/api#op-get-v3-broker-search&quot;&gt;Broker search API&lt;/a&gt;
(&lt;code&gt;GET /v3/broker/search&lt;/code&gt; with &lt;code&gt;ts_start&lt;/code&gt;, &lt;code&gt;ts_end&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt;,
&lt;code&gt;collector_id&lt;/code&gt;, &lt;code&gt;data_type=updates&lt;/code&gt;), then slice the hijack out of the
update stream:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search -t 1787950650 -d 86400 -c rrc00 -p &quot;162.55.80.0/24&quot; -a &quot;62390&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 144-byte record dissected above is also built into wirescope as a
sample (&quot;UPDATE · hijack, real MRT record, RIS rrc00&quot;), and the deep link
above reproduces it from the URL alone. The extraction used the
&lt;a href=&quot;https://bgpkit.com/tools/parser/&quot;&gt;bgpkit-parser&lt;/a&gt; &lt;code&gt;RawMrtRecord&lt;/code&gt; API: parse the archive,
match the record, dump &lt;code&gt;raw_bytes()&lt;/code&gt;, paste into wirescope. A fuller case-study writeup,
including the multi-collector timeline verification and ROA history, is
available on request.&lt;/p&gt;
&lt;h2&gt;Sources&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Virtualizor / Softaculous, &lt;em&gt;Security Incident: BGP Hijacking&lt;/em&gt;, 2026-08-31.
&lt;a href=&quot;https://www.virtualizor.com/blog/security-incident-bgp-hijacking/&quot;&gt;https://www.virtualizor.com/blog/security-incident-bgp-hijacking/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Neo23x0/signature-base PR #417, YARA rules and IoCs for the Virtualizor compromise, 2026-08-31.
&lt;a href=&quot;https://github.com/Neo23x0/signature-base/pull/417&quot;&gt;https://github.com/Neo23x0/signature-base/pull/417&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LowEndTalk, &lt;em&gt;URGENT: Virtualizor Compromised (31st AUG)&lt;/em&gt;: operator reports from affected hosts.
&lt;a href=&quot;https://lowendtalk.com/discussion/220625/urgent-virtualizor-compromised-31st-aug/p1&quot;&gt;https://lowendtalk.com/discussion/220625/urgent-virtualizor-compromised-31st-aug/p1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RIPE RIS raw MRT archives (rrc00, rrc01) and Route Views (route-views2), 2026-08-28 through 08-30.&lt;/li&gt;
&lt;li&gt;PeeringDB records for AS62390 (NEXONHOST) and AS6204 (ZET.NET), retrieved 2026-08-31.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bgpkit.com/api#op-get-v3-roas-search&quot;&gt;BGPKIT ROA history API&lt;/a&gt;, daily ROA snapshots 2011-present.&lt;/li&gt;
&lt;li&gt;rpki-client global ASPA export (NTT), 2026-09-01 build. &lt;a href=&quot;https://rpki.gin.ntt.net/api/export.json&quot;&gt;https://rpki.gin.ntt.net/api/export.json&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bryton Herdes on the minimal-ROA counterfactual for this incident. &lt;a href=&quot;https://x.com/next_hopself/status/2094553871087509799&quot;&gt;https://x.com/next_hopself/status/2094553871087509799&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Don&apos;t Deserialize: Serving 1.5M RPKI ROA Records from a Memory-Mapped Trie</title><link>https://bgpkit.com/blog/zero-copy-prefix-tries-with-rkyv/</link><guid isPermaLink="true">https://bgpkit.com/blog/zero-copy-prefix-tries-with-rkyv/</guid><description>How wayback-rpki 2.0 cut steady-state RSS from 1.30 GB to around 500 MB and startup from 2.3 s to 23 ms with rkyv memory-mapped archives and jemalloc.</description><pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;How &lt;a href=&quot;https://github.com/bgpkit/wayback-rpki&quot;&gt;wayback-rpki&lt;/a&gt; went from 1.30 GB of
resident memory to about 500 MB — without changing a single query result.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/wayback-rpki&quot;&gt;Wayback RPKI&lt;/a&gt; is a BGPKIT service that
crawls every daily RPKI Route Origin Authorization (ROA) dump from RIPE since
2011 and serves a REST API for historical lookups. The full dataset is
substantial: &lt;strong&gt;1,108,337 unique prefixes&lt;/strong&gt; carrying &lt;strong&gt;1,531,339 ROA records&lt;/strong&gt;,
each annotated with date ranges spanning up to 15 years of history.&lt;/p&gt;
&lt;p&gt;For years, the service ran fine. After its bootstrap and initial update, its
glibc allocator retained a &lt;strong&gt;1.30 GB&lt;/strong&gt; resident-memory high-water mark, and it
took 2.3 seconds to start. This post explains how the v2 design cuts
steady-state RSS to &lt;strong&gt;around 500 MB&lt;/strong&gt; and cold-start-to-first-query to &lt;strong&gt;23 ms&lt;/strong&gt;:
&lt;a href=&quot;https://github.com/rkyv/rkyv&quot;&gt;rkyv&lt;/a&gt; serves the trie from a memory-mapped
archive, while jemalloc returns the bootstrap and update allocations that are
no longer live to the OS.&lt;/p&gt;
&lt;p&gt;The technique is not specific to RPKI data. Any Rust service that loads a large,
read-heavy data structure at startup — a routing table, a geolocation database,
a blocklist — can apply the same pattern.&lt;/p&gt;
&lt;h2&gt;The problem: deserialize-everything-at-startup&lt;/h2&gt;
&lt;p&gt;The v1 architecture was straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RIPE daily dumps → parse_roas_csv → ipnet-trie&amp;#x3C;HashMap&amp;#x3C;...&gt;&gt; → bincode → .bin.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At startup, the service read the &lt;code&gt;.bin.gz&lt;/code&gt; archive and &lt;strong&gt;deserialized the entire
trie into heap memory&lt;/strong&gt;. Every &lt;code&gt;HashMap&lt;/code&gt;, every &lt;code&gt;HashSet&amp;#x3C;i64&gt;&lt;/code&gt; of dates, every
&lt;code&gt;VecDeque&lt;/code&gt; of compressed ranges — all reconstructed into native Rust collections
before the first query could be served.&lt;/p&gt;
&lt;p&gt;On the production dataset, this meant:&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;v1 (bincode + heap)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Startup + first query&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;2.3 s&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Steady-state RSS after bootstrap/update&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;1.30 GB&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;On-disk archive (gzipped)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;12 MB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The 1.30 GB figure is real resident memory, not virtual. The trie&apos;s node
structure, combined with per-record &lt;code&gt;HashSet&lt;/code&gt; and &lt;code&gt;VecDeque&lt;/code&gt; allocations, created
a massive heap working set. The data was inherently read-only after load — but
the runtime had no way to know that. It dutifully rebuilt every collection.&lt;/p&gt;
&lt;p&gt;The fix was obvious in retrospect: &lt;strong&gt;don&apos;t deserialize at all.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;rkyv in one paragraph&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rkyv/rkyv&quot;&gt;rkyv&lt;/a&gt; is a Rust serialization framework that
produces &lt;strong&gt;zero-copy archives&lt;/strong&gt;. Instead of writing data in a transport format
that must be parsed back into native types, rkyv lays out the bytes in a shape
that can be &lt;em&gt;read directly&lt;/em&gt; as native Rust types — no allocation, no copy, no
parsing. You derive &lt;code&gt;Archive&lt;/code&gt; on a struct; rkyv generates a corresponding
&lt;code&gt;ArchivedFoo&lt;/code&gt; type whose fields are byte-level-compatible with the on-disk
representation. A pointer into the mmap&apos;d file &lt;em&gt;is&lt;/em&gt; a valid &lt;code&gt;&amp;#x26;ArchivedFoo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The trade-off: rkyv archives are not portable across architectures (endianness,
pointer width). That is acceptable — even desirable — for a local serving
archive that is generated and consumed on the same platform class. The rkyv
support in &lt;code&gt;prefix-trie&lt;/code&gt; was introduced in the
&lt;a href=&quot;https://github.com/tiborschneider/prefix-trie/releases/tag/v0.10.0&quot;&gt;v0.10.0 release&lt;/a&gt;
(also published as &lt;a href=&quot;https://crates.io/crates/prefix-trie/0.10.1&quot;&gt;0.10.1 on crates.io&lt;/a&gt;),
which is what made this migration possible.&lt;/p&gt;
&lt;h2&gt;v1 vs v2 at a glance&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;{`flowchart LR
    subgraph v1[&quot;v1: bincode + heap&quot;]
        direction TB
        A1[&quot;.bin.gz (12 MB)&quot;] --&gt;|bincode deserialize| B1[&quot;Heap trie\n1.30 GB RSS\n2.3s startup&quot;]
    end
    subgraph v2[&quot;v2: rkyv + mmap&quot;]
        direction TB
        A2[&quot;.rkyv (128 MB raw)&quot;] --&gt;|mmap + jemalloc| B2[&quot;Page cache\nabout 500 MB RSS\n23ms startup&quot;]
    end
    v1 -.-&gt;|&quot;100× faster startup\nabout 2.5× lower RSS&quot;| v2`}&lt;/pre&gt;
&lt;h2&gt;The migration&lt;/h2&gt;
&lt;h3&gt;Step 1: prefix-trie with rkyv support&lt;/h3&gt;
&lt;p&gt;We replaced &lt;a href=&quot;https://crates.io/crates/ipnet-trie&quot;&gt;&lt;code&gt;ipnet-trie&lt;/code&gt;&lt;/a&gt; with
&lt;a href=&quot;https://crates.io/crates/prefix-trie&quot;&gt;&lt;code&gt;prefix-trie&lt;/code&gt;&lt;/a&gt;, which ships rkyv support
via its &lt;code&gt;rkyv&lt;/code&gt; feature. The core data type changed from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// v1: IpnetTrie keyed on HashMap — not serializable as a zero-copy structure
IpnetTrie&amp;#x3C;HashMap&amp;#x3C;(u8, u32), RoasTrieEntry&gt;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// v2: JointPrefixMap is rkyv-derivable
JointPrefixMap&amp;#x3C;IpNet, Vec&amp;#x3C;RoaRecord&gt;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;JointPrefixMap&lt;/code&gt; is a combined IPv4/IPv6 radix trie that natively implements
&lt;code&gt;Archive&lt;/code&gt;/&lt;code&gt;Serialize&lt;/code&gt;/&lt;code&gt;Deserialize&lt;/code&gt;. It also provides &lt;code&gt;get_spm()&lt;/code&gt; (shortest
prefix match) and &lt;code&gt;TrieView&lt;/code&gt; for subtree iteration — the exact operations needed
for ROA validation and inclusive prefix search.&lt;/p&gt;
&lt;h3&gt;Step 2: rkyv-derivable record types&lt;/h3&gt;
&lt;p&gt;The per-prefix ROA record changed from a heap-heavy struct using &lt;code&gt;HashSet&lt;/code&gt; +
&lt;code&gt;VecDeque&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// v1: dual storage, both heap-allocated collections
struct RoasTrieEntry {
    max_len: u8,
    origin: u32,
    dates: HashSet&amp;#x3C;i64&gt;,              // uncompressed, during bootstrap
    dates_compressed: VecDeque&amp;#x3C;(i64, i64)&gt;,  // merged ranges
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to a lean, rkyv-friendly struct:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[derive(Archive, Serialize, Deserialize)]
pub struct RoaRecord {
    pub max_len: u8,
    pub origin: u32,
    pub dates: Vec&amp;#x3C;(i64, i64)&gt;,   // compressed (start, end) ranges, always
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Date compression still happens — but at &lt;em&gt;build&lt;/em&gt; time, not query time. During
bootstrap, the builder (&lt;code&gt;RoasTrieMut&lt;/code&gt;) collects dates into a &lt;code&gt;HashSet&lt;/code&gt;, then
calls &lt;code&gt;full_compress()&lt;/code&gt; to merge consecutive days into &lt;code&gt;(start, end)&lt;/code&gt; ranges
before serialization. The archive on disk only ever contains the compressed
ranges. No &lt;code&gt;HashSet&lt;/code&gt;, no &lt;code&gt;VecDeque&lt;/code&gt; — just a flat &lt;code&gt;Vec&amp;#x3C;(i64, i64)&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Step 3: the archive root&lt;/h3&gt;
&lt;p&gt;The top-level archive type carries header metadata alongside the trie:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[derive(Archive, Serialize, Deserialize)]
pub struct RoasTrieData {
    pub format_version: u32,
    pub latest_date: i64,
    pub ipv4_count: u64,
    pub ipv6_count: u64,
    pub trie: JointPrefixMap&amp;#x3C;IpNet, Vec&amp;#x3C;RoaRecord&gt;&gt;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ipv4_count&lt;/code&gt; and &lt;code&gt;ipv6_count&lt;/code&gt; are pre-computed at build time. In v1, &lt;code&gt;/health&lt;/code&gt;
had to iterate the entire trie to produce per-family counts — an O(n) scan on
every health check. Now it reads two integers from the header. &lt;code&gt;latest_date&lt;/code&gt; is
similarly cached, eliminating another full scan.&lt;/p&gt;
&lt;h3&gt;Step 4: mmap and query without deserializing&lt;/h3&gt;
&lt;p&gt;The read-side &lt;code&gt;RoasTrie&lt;/code&gt; struct holds either a memory map or owned bytes, and
provides queries that run &lt;em&gt;directly against the archived bytes&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub struct RoasTrie {
    bytes: TrieBytes,
}

enum TrieBytes {
    Mmap(memmap2::Mmap),
    Owned(Vec&amp;#x3C;u8&gt;),
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Opening the archive is a single &lt;code&gt;mmap&lt;/code&gt; call plus a one-time validation pass:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub fn open(path: &amp;#x26;str) -&gt; Result&amp;#x3C;Self&gt; {
    let file = std::fs::File::open(path)?;
    let mmap = unsafe { memmap2::Mmap::map(&amp;#x26;file)? };
    let trie = RoasTrie { bytes: TrieBytes::Mmap(mmap) };
    trie.validate_bytes()?;
    Ok(trie)
}

fn validate_bytes(&amp;#x26;self) -&gt; Result&amp;#x3C;()&gt; {
    let data: &amp;#x26;ArchivedRoasTrieData =
        rkyv::access::&amp;#x3C;_, rkyv::rancor::Error&gt;(self.bytes.as_slice())?;
    // ... version check ...
    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After validation, every subsequent access skips the check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn data(&amp;#x26;self) -&gt; &amp;#x26;ArchivedRoasTrieData {
    // SAFETY: validated at open(), immutable for lifetime of this struct
    unsafe { rkyv::access_unchecked::&amp;#x3C;ArchivedRoasTrieData&gt;(self.bytes.as_slice()) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Queries operate on &lt;code&gt;ArchivedRoaRecord&lt;/code&gt; references — no &lt;code&gt;.to_native()&lt;/code&gt; deserialization
into owned types until the final conversion to the API response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub fn validate(&amp;#x26;self, prefix: &amp;#x26;IpNet, origin: u32, date_ts: i64) -&gt; RpkiValidation {
    for (_p, records) in self.match_records(prefix) {
        for r in records.iter() {
            let r_origin = r.origin.to_native();     // cheap integer fixup
            let r_max_len = r.max_len;                // already native-width
            if r_origin == origin &amp;#x26;&amp;#x26; r_max_len &gt;= prefix.prefix_len()
                &amp;#x26;&amp;#x26; record_contains_date(r, date_ts)
            {
                return RpkiValidation::Valid;
            }
        }
        // ... Invalid / Unknown logic ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;r.origin&lt;/code&gt; is an &lt;code&gt;Archived&amp;#x3C;u32&gt;&lt;/code&gt;. On a little-endian host (which is what we
deploy on), &lt;code&gt;to_native()&lt;/code&gt; is a no-op at the hardware level — the bytes in the
mmap&apos;d file are already in native byte order. The &quot;deserialization&quot; is just a
type-system formality.&lt;/p&gt;
&lt;h3&gt;Step 5: safe hot-reload via atomic rename&lt;/h3&gt;
&lt;p&gt;The service updates its trie every 8 hours. With the entire data structure
living in a mmap&apos;d file, we cannot update it in place. Instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The background update thread loads the archive into a mutable &lt;code&gt;RoasTrieMut&lt;/code&gt;,
applies new ROA data, and calls &lt;code&gt;dump()&lt;/code&gt; — which writes to a &lt;code&gt;.tmp&lt;/code&gt; file and
then &lt;code&gt;rename&lt;/code&gt;s it atomically over the live path.&lt;/li&gt;
&lt;li&gt;Readers holding the old &lt;code&gt;Mmap&lt;/code&gt; handle continue querying the old bytes (the
kernel keeps the inode alive until all mappings close).&lt;/li&gt;
&lt;li&gt;The next reload opens the new file and re-mmaps.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub fn dump(&amp;#x26;mut self, path: &amp;#x26;str) -&gt; Result&amp;#x3C;()&gt; {
    // ... compress, build RoasTrieData, rkyv::to_bytes ...
    let tmp_path = format!(&quot;{}.tmp&quot;, path);
    std::fs::write(&amp;#x26;tmp_path, &amp;#x26;bytes)?;
    std::fs::rename(&amp;#x26;tmp_path, path)?;
    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No locks, no torn reads, no query downtime.&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;{`sequenceDiagram
    participant API as API server (mmap, inode A)
    participant FS as Filesystem
    participant BG as Background updater

    API-&gt;&gt;FS: mmap roas_trie.rkyv (inode A)
    BG-&gt;&gt;BG: load + update RoasTrieMut
    BG-&gt;&gt;FS: write roas_trie.rkyv.tmp
    BG-&gt;&gt;FS: atomic rename → roas_trie.rkyv (inode B)
    Note over API,FS: readers on inode A unaffected
    API-&gt;&gt;FS: reopen + mmap roas_trie.rkyv (inode B)
    Note over FS: kernel evicts inode A after last reader closes`}&lt;/pre&gt;
&lt;h3&gt;Step 6: an allocator that gives memory back&lt;/h3&gt;
&lt;p&gt;The mmap eliminates the heap-resident trie, but the process still allocates
heavily at two points: bootstrap, where the JSONL import builds a mutable trie
that peaks at roughly 200 MB, and every 8-hour update, which loads, mutates,
and re-serializes the archive. With glibc&apos;s malloc, those freed allocations
were never returned — freed arenas are retained for future reuse, and the
process sat permanently at a 1.28 GB high-water mark even though its live
working set was far smaller.&lt;/p&gt;
&lt;p&gt;The fix is a two-line allocator swap
(&lt;a href=&quot;https://crates.io/crates/tikv-jemallocator&quot;&gt;&lt;code&gt;tikv-jemallocator&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[global_allocator]
static ALLOC: Jemalloc = Jemalloc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;jemalloc&apos;s decay-based purging returns dirty pages to the operating system
after they sit unused, rather than retaining them indefinitely. After a
bootstrap or update completes, the process settles back to around 500 MB
instead of ratcheting up to the glibc high-water mark. Between the two
changes, the serving heap stays small: the dominant resident share is the
mapped archive&apos;s file-backed pages, which the kernel can evict under pressure
and re-fault on demand.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;On the full production dataset (1,108,337 prefixes, 1,531,339 ROA records):&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;v1 (bincode + heap)&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;v2 (rkyv + mmap)&lt;/th&gt;&lt;th&gt;Improvement&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Startup + first query&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2.3 s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;23 ms&lt;/td&gt;&lt;td&gt;&lt;strong&gt;100×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Steady-state RSS&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1.30 GB&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;around 500 MB&lt;/td&gt;&lt;td&gt;&lt;strong&gt;about 2.5×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Full-scan API query (1.5M records, paginated)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unmeasured (full in-memory materialization)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;244 ms&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;On-disk archive (raw)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;—&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;128 MB&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;On-disk archive (gzipped, transport)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;12 MB&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;13 MB&lt;/td&gt;&lt;td&gt;≈ same&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/health&lt;/code&gt; per-family counts&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(n) scan&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;O(1) header read&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The 128 MB raw archive is larger than the 12 MB gzipped v1 blob — expected,
since rkyv archives are uncompressed (mmap requires raw bytes). For transport
and backup, the service uses the platform-agnostic &lt;code&gt;.jsonl.gz&lt;/code&gt; format. Bootstrap
streams that transport into a mutable builder, which writes the raw local
archive that is then mmap&apos;d. The active serving archive is always raw.&lt;/p&gt;
&lt;p&gt;The service settles around 500 MB of RSS in production; the exact value varies
with the archive-backed working set and recent update activity. The memory map
avoids rebuilding the full trie as heap objects, while jemalloc releases
bootstrap and update allocations that glibc retained. Importing the JSONL
transport builds a mutable trie that peaks at roughly 200 MB; after an update,
jemalloc purges those freed allocations instead of leaving the process at
glibc&apos;s 1.28 GB high-water mark.
The zero-copy query path ends up looking like this:&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;{`flowchart TB
    REQ[&quot;GET /search?prefix=1.1.1.0/24&quot;] --&gt; ROUTER[&quot;Axum handler&quot;]
    ROUTER --&gt;|zero-copy| ACCESS[&quot;access_unchecked(bytes)&quot;]
    ACCESS --&gt; TRIE[&quot;ArchivedRoasTrieData.trie&quot;]
    TRIE --&gt;|get_spm| NODE[&quot;multi-bit node walk\n(≤7 hops for IPv4)&quot;]
    NODE --&gt; REC[&quot;ArchivedRoaRecord\n.max_len, .origin, .dates&quot;]
    REC --&gt;|to_native| RESP[&quot;JSON response&quot;]
    style TRIE fill:none,stroke-dasharray: 5 5
    style NODE fill:none,stroke-dasharray: 5 5
    style REC fill:none,stroke-dasharray: 5 5`}&lt;/pre&gt;
&lt;h3&gt;Correctness verification&lt;/h3&gt;
&lt;p&gt;The migration was verified by a differential audit against the v1 production
archive. After converting &lt;code&gt;roas_trie.bin.gz&lt;/code&gt; → &lt;code&gt;roas_trie.rkyv&lt;/code&gt;, every query
(&lt;code&gt;search&lt;/code&gt;, &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;lookup_prefix&lt;/code&gt;) returns &lt;strong&gt;byte-identical result sets&lt;/strong&gt;.
The only behavioral difference: v1 returned per-prefix records in nondeterministic
&lt;code&gt;HashMap&lt;/code&gt; order; v2 sorts by &lt;code&gt;(origin, max_len)&lt;/code&gt; for deterministic output — a
fix, not a regression.&lt;/p&gt;
&lt;h2&gt;Practical lessons&lt;/h2&gt;
&lt;h3&gt;rkyv archived types are not transparent&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ArchivedRoasTrieData&lt;/code&gt; is a distinct type from &lt;code&gt;RoasTrieData&lt;/code&gt;. Its fields are
&lt;code&gt;Archived&amp;#x3C;T&gt;&lt;/code&gt; wrappers: &lt;code&gt;ArchivedVec&amp;#x3C;ArchivedRoaRecord&gt;&lt;/code&gt; instead of
&lt;code&gt;Vec&amp;#x3C;RoaRecord&gt;&lt;/code&gt;, &lt;code&gt;Archived&amp;#x3C;u32&gt;&lt;/code&gt; instead of &lt;code&gt;u32&lt;/code&gt;. You cannot pattern-match
archived tuples directly — &lt;code&gt;ArchivedTuple2&lt;/code&gt; is not destructurable, so you access
fields with &lt;code&gt;.0&lt;/code&gt; and &lt;code&gt;.1&lt;/code&gt;. These are paper cuts, not blockers, but they require
discipline: the query layer must consistently work with archived types rather
than reaching for &lt;code&gt;.to_native()&lt;/code&gt; prematurely.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;prefix-trie&lt;/code&gt;&apos;s archived joint map exposes internal family fields&lt;/h3&gt;
&lt;p&gt;The archived &lt;code&gt;JointPrefixMap&lt;/code&gt; exposes &lt;code&gt;t1&lt;/code&gt; (IPv4) and &lt;code&gt;t2&lt;/code&gt; (IPv6) as separate
&lt;code&gt;ArchivedPrefixMap&lt;/code&gt; fields. Subtree iteration — needed for the &quot;include supernets
and subnets&quot; search mode — goes through &lt;code&gt;AsView::view_at&lt;/code&gt; + &lt;code&gt;TrieView::iter&lt;/code&gt;,
which yields &lt;code&gt;(IpNet, &amp;#x26;ArchivedVec&amp;#x3C;...&gt;)&lt;/code&gt; by value. This is slightly awkward but
workable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn match_records(&amp;#x26;self, prefix: &amp;#x26;IpNet)
    -&gt; Vec&amp;#x3C;(IpNet, &amp;#x26;ArchivedVec&amp;#x3C;ArchivedRoaRecord&gt;)&gt;
{
    let data = self.data();
    let spm = match data.trie.get_spm(prefix) {
        Some((spm, _)) =&gt; spm,
        None =&gt; return vec![],
    };
    match spm {
        IpNet::V4(spm4) =&gt; match data.trie.t1.view_at(&amp;#x26;spm4) {
            Some(view) =&gt; view.iter()
                .map(|(p, recs)| (IpNet::V4(p), recs))
                .collect(),
            None =&gt; vec![],
        },
        IpNet::V6(spm6) =&gt; match data.trie.t2.view_at(&amp;#x26;spm6) {
            // ... same for IPv6 ...
        },
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Backward compatibility costs one enum, not an architecture&lt;/h3&gt;
&lt;p&gt;During the transition, the API layer uses a &lt;code&gt;TrieBackend&lt;/code&gt; enum that routes to
either the v2 mmap backend or the v1 legacy in-memory backend based on the file
suffix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub enum TrieBackend {
    V2(RoasTrie),       // .rkyv → mmap
    V1(LegacyRoasTrie), // .bin / .bin.gz → heap
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both implement the same query surface (&lt;code&gt;search&lt;/code&gt;, &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;counts&lt;/code&gt;,
&lt;code&gt;latest_date_ts&lt;/code&gt;). The &lt;code&gt;/health&lt;/code&gt; endpoint reports &lt;code&gt;format_version: 1 | 2&lt;/code&gt;.
A missing &lt;code&gt;.rkyv&lt;/code&gt; file is automatically generated from a sibling &lt;code&gt;.bin.gz&lt;/code&gt;
before any remote download is attempted. This costs ~600 lines of legacy
shim code, but means zero downtime migration: cronjobs can keep producing &lt;code&gt;.bin&lt;/code&gt;
files while live APIs serve from &lt;code&gt;.rkyv&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;When to use this pattern&lt;/h2&gt;
&lt;p&gt;This approach is a good fit when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The dataset is large and read-only at query time.&lt;/strong&gt; If you need frequent
mutations, rkyv&apos;s immutable archive forces a full re-serialize on every
update.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Startup latency matters.&lt;/strong&gt; If your service restarts frequently (autoscaling,
serverless, crash recovery), 23 ms vs 2.3 s is the difference between
&quot;invisible&quot; and &quot;noticeable outage.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory is constrained or shared.&lt;/strong&gt; Multiple processes can mmap the same
archive file; the kernel deduplicates the pages. The trie effectively becomes
a shared read-only resource backed by the OS page cache.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You control the archive generation.&lt;/strong&gt; Since rkyv archives are
platform-specific, you generate them on (or for) the target architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is &lt;em&gt;not&lt;/em&gt; a good fit for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross-platform data exchange (use serde + a portable format).&lt;/li&gt;
&lt;li&gt;Write-heavy workloads (use a real database or in-memory mutable structure).&lt;/li&gt;
&lt;li&gt;Datasets smaller than ~10 MB where bincode deserialization is already
negligible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Try it against the live API&lt;/h2&gt;
&lt;p&gt;The service now backs the RPKI ROA endpoint in the public BGPKIT API:
&lt;a href=&quot;https://api.bgpkit.com/v3/roas/search&quot;&gt;&lt;code&gt;GET /v3/roas/search&lt;/code&gt;&lt;/a&gt;. The API gateway
forwards the same search parameters (&lt;code&gt;prefix&lt;/code&gt;, &lt;code&gt;asn&lt;/code&gt;, &lt;code&gt;max_len&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt;,
&lt;code&gt;current&lt;/code&gt;, &lt;code&gt;exact&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, and &lt;code&gt;page_size&lt;/code&gt;) to wayback-rpki, so a caller can
retrieve historical ROAs alongside the other &lt;code&gt;api.bgpkit.com&lt;/code&gt; services. The
&lt;code&gt;date&lt;/code&gt; filter selects ROAs active on that day; each matching record still
returns its complete observed date-range history.&lt;/p&gt;
&lt;p&gt;For example, a historical lookup for Cloudflare&apos;s &lt;code&gt;1.1.1.0/24&lt;/code&gt; (AS13335)
returns one ROA record:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl &quot;https://api.bgpkit.com/v3/roas/search?prefix=1.1.1.0/24&amp;#x26;date=2020-06-01&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;total&quot;: 1,
  &quot;error&quot;: null,
  &quot;data&quot;: [
    {
      &quot;prefix&quot;: &quot;1.1.1.0/24&quot;,
      &quot;max_len&quot;: 24,
      &quot;asn&quot;: 13335,
      &quot;date_ranges&quot;: [
        [&quot;2018-04-05&quot;, &quot;2026-07-28&quot;]
      ],
      &quot;current&quot;: true
    }
  ],
  &quot;meta&quot;: {
    &quot;latest_date&quot;: &quot;2026-07-28&quot;,
    &quot;format_version&quot;: 2,
    &quot;backend&quot;: &quot;railway&quot;
  },
  &quot;page&quot;: 0,
  &quot;page_size&quot;: 100
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the service yourself, the one-command bootstrap downloads a pre-built
archive, builds the local &lt;code&gt;.rkyv&lt;/code&gt; file, and memory-maps it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install wayback-rpki
wayback-rpki serve --bootstrap
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The full implementation lives in
&lt;a href=&quot;https://github.com/bgpkit/wayback-rpki/pull/12&quot;&gt;wayback-rpki PR #12&lt;/a&gt; and the
&lt;a href=&quot;https://github.com/bgpkit/wayback-rpki/blob/main/src/roas_trie.rs&quot;&gt;&lt;code&gt;roas_trie.rs&lt;/code&gt;&lt;/a&gt;
module. The production service has been serving from the rkyv+mmap backend since
July 2026.&lt;/p&gt;
&lt;p&gt;The core insight is simple: &lt;strong&gt;if your data is read-only at query time, don&apos;t
pay for deserialization.&lt;/strong&gt; Memory-map the file, derive &lt;code&gt;Archive&lt;/code&gt;, and let the
kernel manage your working set. Pair it with an allocator that gives freed
bootstrap memory back: 1.30 GB becomes about 500 MB, startup becomes unnoticeable,
and the query code barely changes.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;BGPKIT builds open-source BGP data analysis tools in Rust. &lt;a href=&quot;https://github.com/bgpkit/wayback-rpki&quot;&gt;Wayback RPKI&lt;/a&gt;
provides historical RPKI ROA data through the free public
&lt;a href=&quot;https://api.bgpkit.com/v3/roas/search&quot;&gt;&lt;code&gt;api.bgpkit.com/v3/roas/search&lt;/code&gt;&lt;/a&gt; API.&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>monocle v1.4.0: Remote BGP Search, PCH Archive Parsing, and RIB Correctness</title><link>https://bgpkit.com/blog/monocle-v1-4-0/</link><guid isPermaLink="true">https://bgpkit.com/blog/monocle-v1-4-0/</guid><description>monocle v1.4.0 adds an HTTP/SSE service, Cisco BGP-table parsing, reusable filters, RPKISPOOL historical data, and RIB-search correctness fixes.</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Public BGP analysis is frequently constrained by both the source data and the machine doing the work. A single investigation may need to parse many MRT files, ingest &lt;a href=&quot;https://downloads.pch.net/BGP_Routing_Data/&quot;&gt;Packet Clearing House&apos;s Cisco-style route dumps&lt;/a&gt;, and compare an older route observation with the RIB and RPKI state from the same period. Doing all of that on an analyst&apos;s working machine is impractical when it lacks the CPU, memory, disk, or persistent cache required for the data volume.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/monocle/releases/tag/v1.4.0&quot;&gt;&lt;code&gt;monocle&lt;/code&gt; v1.4.0&lt;/a&gt; connects those requirements into one workflow. Its HTTP/SSE service moves downloads and parsing to a dedicated server, cloud VM, or container deployment while streaming matching results back to the working machine. The same release adds PCH text-dump parsing, reusable file-based filters, and RPKISPOOL-backed historical RPKI lookups, and corrects RIB and RIB-search behavior that could omit stable routes whose per-route timestamps predate the snapshot.&lt;/p&gt;
&lt;h2&gt;Install and run&lt;/h2&gt;
&lt;p&gt;The release can be installed with Cargo or Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install monocle --version 1.4.0
# or, on macOS and Linux with Homebrew
brew install monocle

monocle --version
# monocle 1.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A multi-architecture &lt;a href=&quot;https://hub.docker.com/r/bgpkit/monocle/tags&quot;&gt;&lt;code&gt;bgpkit/monocle&lt;/code&gt; Docker image is available on Docker Hub&lt;/a&gt;. It provides a containerized deployment path for a dedicated search host or an orchestrated environment such as Kubernetes, where the service can retain its own database and MRT-file cache instead of relying on the workstation that submits a query.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run --rm \
  -p 8080:8080 \
  -v monocle-data:/data \
  bgpkit/monocle:1.4.0 \
  server --address 0.0.0.0 --port 8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The persistent &lt;code&gt;/data&lt;/code&gt; volume holds monocle&apos;s SQLite-backed data. Add a separate cache mount and configure &lt;code&gt;cache_dir&lt;/code&gt; when repeatedly searching historical MRT files. With the binary or container in place, the next decision is whether the working machine should execute the search itself or submit it to a dedicated service.&lt;/p&gt;
&lt;h2&gt;Run searches where the data and compute are&lt;/h2&gt;
&lt;p&gt;For a one-off query, local &lt;code&gt;monocle search&lt;/code&gt; remains the shortest path. Longer investigations, repeated queries, and applications benefit from separating the working machine from the search host: the latter can keep the MRT cache and provide the CPU, memory, disk, and network capacity required to retrieve and parse public archives.&lt;/p&gt;
&lt;p&gt;v1.4.0 replaces the previous WebSocket service with HTTP. Search results arrive as Server-Sent Events (SSE); health checks, metadata, lookups, and database management use ordinary REST endpoints. This lets the working machine submit a narrow query, while the remote service does the download and parsing work and streams matching elements and progress back as they become available.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# On the search host
monocle server --address 0.0.0.0 --port 8080

# A health check remains unauthenticated, even when API authentication is enabled.
curl http://127.0.0.1:8080/health
# OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service exposes &lt;code&gt;GET /health&lt;/code&gt;, &lt;code&gt;GET /api/v1/system/info&lt;/code&gt;, and &lt;code&gt;POST /api/v1/search/stream&lt;/code&gt;, as well as endpoints for time, country, IP, RPKI, prefix-to-ASN, AS relationships, inspection, and database operations. Database-backed endpoints return &lt;code&gt;503 NOT_INITIALIZED&lt;/code&gt; until their data has been explicitly refreshed; a request does not trigger an implicit refresh.&lt;/p&gt;
&lt;p&gt;The SSE endpoint accepts the same kinds of filters as the CLI. For example, this asks RIPE RIS collector &lt;code&gt;rrc00&lt;/code&gt; for updates concerning Cloudflare&apos;s &lt;code&gt;1.1.1.0/24&lt;/code&gt; in a ten-minute window:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -N \
  -H &apos;Accept: text/event-stream&apos; \
  -H &apos;Content-Type: application/json&apos; \
  -d &apos;{
    &quot;filters&quot;: {
      &quot;prefix&quot;: [&quot;1.1.1.0/24&quot;],
      &quot;start_ts&quot;: &quot;2025-06-01T00:00:00Z&quot;,
      &quot;end_ts&quot;: &quot;2025-06-01T00:10:00Z&quot;,
      &quot;collector&quot;: &quot;rrc00&quot;,
      &quot;project&quot;: &quot;riperis&quot;,
      &quot;dump_type&quot;: &quot;updates&quot;
    },
    &quot;batch_size&quot;: 100
  }&apos; \
  http://127.0.0.1:8080/api/v1/search/stream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A stream begins with &lt;code&gt;started&lt;/code&gt;, may emit &lt;code&gt;progress&lt;/code&gt; and &lt;code&gt;elements&lt;/code&gt;, and terminates with exactly one &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;cancelled&lt;/code&gt;, or &lt;code&gt;error&lt;/code&gt; event. Its final event includes matched-element and source-file counts, compressed-byte metadata, rate, and the collectors and files that actually produced matches. Disconnecting cancels the work.&lt;/p&gt;
&lt;p&gt;The server also makes its operating limits explicit. &lt;code&gt;server_max_concurrent_searches&lt;/code&gt; defaults to 3 and excess requests receive HTTP 429. A bounded channel protects element batches from being dropped under backpressure; progress updates may be coalesced. &lt;code&gt;server_max_search_batch_size&lt;/code&gt;, &lt;code&gt;server_max_search_results&lt;/code&gt;, &lt;code&gt;server_search_timeout_secs&lt;/code&gt;, and &lt;code&gt;search_concurrency&lt;/code&gt; can be set in &lt;code&gt;monocle.toml&lt;/code&gt;, through &lt;code&gt;MONOCLE_*&lt;/code&gt; environment variables, or with server flags. Token authentication protects &lt;code&gt;/api/v1/*&lt;/code&gt; while leaving &lt;code&gt;/health&lt;/code&gt; available for container health checks.&lt;/p&gt;
&lt;p&gt;The CLI can submit the same search to that remote service. This keeps scripts and output formatting local while moving downloads, parsing, and cached data to the server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search \
  -p 1.1.1.0/24 \
  -t 2025-06-01T00:00:00Z \
  -T 2025-06-01T00:10:00Z \
  -c rrc00 -P riperis \
  --remote-url https://monocle.example.net/api/v1/search/stream \
  --remote-token &quot;$MONOCLE_REMOTE_TOKEN&quot; \
  --format json-line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--remote-url&lt;/code&gt; consumes the SSE stream and uses monocle&apos;s existing formatters. Invalid requests and pre-stream validation failures return HTTP errors; a stream that ends in &lt;code&gt;error&lt;/code&gt; or &lt;code&gt;cancelled&lt;/code&gt;, or drops without &lt;code&gt;completed&lt;/code&gt;, makes the CLI exit non-zero. Remote execution addresses the compute constraint, but a useful search service must also accept the archive formats that researchers and operators actually have available.&lt;/p&gt;
&lt;h2&gt;Parse PCH&apos;s Cisco-style BGP-table archives&lt;/h2&gt;
&lt;p&gt;MRT is the usual exchange format for public BGP data, but &lt;a href=&quot;https://downloads.pch.net/BGP_Routing_Data/&quot;&gt;Packet Clearing House&apos;s daily BGP routing archive&lt;/a&gt; publishes its route dumps as fixed-width Cisco-style text. The Cisco parser in v1.4.0 was added specifically so PCH data can enter the same monocle workflow without a separate conversion tool.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;monocle parse&lt;/code&gt; detects the dump preamble and reads the prefix, AS path, next hop, metric, local preference, and origin. It also handles multiple paths for a prefix and layouts where an IPv6 prefix wraps onto its own line.&lt;/p&gt;
&lt;p&gt;The following &lt;a href=&quot;https://downloads.pch.net/files/BGP_Routing_Data/BGP_ROUTES_V4/2025/06/route-collector.acc.pch.net/route-collector.acc.pch.net-ipv4_bgp_routes.2025.06.01.gz&quot;&gt;2025-06-01 Accra collector snapshot&lt;/a&gt; is a 50 KB gzip-compressed example. monocle reads the remote archive directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle parse \
  https://downloads.pch.net/files/BGP_Routing_Data/BGP_ROUTES_V4/2025/06/route-collector.acc.pch.net/route-collector.acc.pch.net-ipv4_bgp_routes.2025.06.01.gz \
  --fields prefix,as_path,next_hop,local_pref,med \
  --format psv | head -5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command produces rows such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1.179.112.0/20|15169 396982|196.201.2.118||0
2.16.77.0/24|35091 30997 20940|196.201.2.6||0
2.16.77.0/24|20940 20940|196.201.2.126||0
2.16.77.0/24|20940|196.201.2.160||0
2.16.77.0/24|37613 3741 37350 30997 20940 20940|196.201.2.32||0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same input can be written as a TABLE_DUMP_V2 RIB for tools that expect MRT:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle parse router-show-ip-bgp.txt \
  --mrt-path router-snapshot.mrt \
  --mrt-type rib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Native support for the PCH text archives is the immediate goal of this release. Broader PCH archive support can follow as the archive formats and workflows are expanded. Once these archives can be queried alongside MRT data, the next practical problem is retaining the query definition as its scope grows.&lt;/p&gt;
&lt;h2&gt;Keep large filters out of shell arguments&lt;/h2&gt;
&lt;p&gt;An investigation that starts with one prefix often grows to hundreds or thousands. v1.4.0 adds &lt;code&gt;--prefix-file&lt;/code&gt; and &lt;code&gt;--filter-file&lt;/code&gt; to both &lt;code&gt;parse&lt;/code&gt; and &lt;code&gt;search&lt;/code&gt;, so that expanded query can be retained in a file instead of being reconstructed from a long shell command.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--prefix-file&lt;/code&gt; is newline-delimited text: blank lines and comments beginning with &lt;code&gt;#&lt;/code&gt; are ignored. &lt;code&gt;--filter-file&lt;/code&gt; is JSON and can hold prefixes, origin or peer ASNs, communities, an AS-path regular expression, element type, and time bounds.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;prefixes&quot;: [&quot;1.1.1.0/24&quot;, &quot;2606:4700::/32&quot;],
  &quot;origin_asns&quot;: [&quot;13335&quot;],
  &quot;peer_asns&quot;: [&quot;174&quot;, &quot;6939&quot;],
  &quot;include_sub&quot;: true,
  &quot;start_ts&quot;: &quot;2025-06-01T00:00:00Z&quot;,
  &quot;end_ts&quot;: &quot;2025-06-01T01:00:00Z&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search \
  --filter-file cloudflare-investigation.json \
  -c rrc00 -P riperis \
  --format json-line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Values from a file merge with command-line values. Values within the same dimension are unioned; different dimensions remain ANDed. This supports versioned, reviewable query definitions without relying on oversized shell arguments. Those definitions can identify the messages around an event; answering what routing state existed at a particular instant requires reconstructing a RIB instead.&lt;/p&gt;
&lt;h2&gt;Reconstruct the RIB that existed at the requested time&lt;/h2&gt;
&lt;p&gt;A RIB dump is not a collection of routes learned only at the dump timestamp. Its per-route timestamps can be days or weeks earlier. Filtering a base RIB by the requested interval therefore discards stable routes and returns an incomplete state.&lt;/p&gt;
&lt;p&gt;v1.4.0 fixes this in &lt;code&gt;monocle rib&lt;/code&gt; and in &lt;code&gt;monocle search --dump-type rib&lt;/code&gt;. The base RIB retains those stable entries, and a target timestamp exactly equal to a dump time selects that dump instead of an unnecessarily earlier snapshot. This avoids both incorrect output and needless update replay.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle rib 2025-06-01T00:00:00Z \
  -c rrc00 -P riperis \
  -p 1.1.1.0/24 \
  --format json-pretty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command selects the latest RIB at or before the target timestamp, then replays updates through that exact time. The result answers a state question, &quot;what routes were present then?&quot;, rather than returning only messages whose timestamps fall in the selected interval. &lt;code&gt;monocle rib&lt;/code&gt; also gains &lt;code&gt;--use-cache&lt;/code&gt;, &lt;code&gt;--cache-dir&lt;/code&gt;, and &lt;code&gt;--fields&lt;/code&gt; in this release, which are useful for repeated or narrowed reconstruction runs. A reconstructed route still does not establish whether its origin was authorized at that time, so the final part of the workflow is a contemporaneous RPKI lookup.&lt;/p&gt;
&lt;h2&gt;Pair reconstructed routes with historical RPKI data&lt;/h2&gt;
&lt;p&gt;Route-origin analysis needs the authorization state from the same period. Applying current ROAs to an old routing event can produce the wrong conclusion. v1.4.0 makes &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-snijders-rpkispool-format/&quot;&gt;RPKISPOOL&lt;/a&gt; the default historical source for &lt;code&gt;monocle rpki roas&lt;/code&gt; and &lt;code&gt;monocle rpki aspas&lt;/code&gt;; the format stores CCR snapshots in &lt;code&gt;.tar.zst&lt;/code&gt; archives. The &lt;code&gt;ripe&lt;/code&gt; and &lt;code&gt;rpkiviews&lt;/code&gt; sources remain available. &lt;a href=&quot;https://www.rpkiviews.org/&quot;&gt;RPKIViews&lt;/a&gt; provides the historical RPKI data used by the &lt;code&gt;rpkiviews&lt;/code&gt; source.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle rpki roas 1.1.1.0/24 \
  --date 2025-06-01 \
  --source rpkispools \
  --format json-pretty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes it possible to pair a reconstructed RIB or an update search with a contemporaneous ROA or ASPA view, rather than treating present-day authorization data as historical evidence. Together, remote execution, PCH parsing, reusable filters, corrected RIB reconstruction, and historical RPKI lookups support an investigation from source discovery through to a time-aligned routing and authorization result.&lt;/p&gt;
&lt;p&gt;The release also adds &lt;code&gt;--concurrency&lt;/code&gt; for local and server searches, improves bulk database refresh behavior, and updates to &lt;code&gt;bgpkit-parser&lt;/code&gt; 0.18.0. TableDumpV2 RIB elements now expose a peer BGP Identifier when it is present, and known-but-unsupported path attributes remain available in the parser&apos;s existing raw-attribute fields instead of being silently discarded. See the &lt;a href=&quot;https://github.com/bgpkit/monocle/releases/tag/v1.4.0&quot;&gt;full v1.4.0 release on GitHub&lt;/a&gt; for the complete change list and release assets.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>bgpkit-parser 0.18.0 Release</title><link>https://bgpkit.com/blog/bgpkit-parser-018-release/</link><guid isPermaLink="true">https://bgpkit.com/blog/bgpkit-parser-018-release/</guid><description>bgpkit-parser 0.18.0 preserves more raw BGP wire data, parses RIS Live raw messages, and adds typed parsers for AIGP, SFP, BFD Discriminator, BGP Prefix-SID, and BIER attributes.</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.18.0&quot;&gt;&lt;code&gt;bgpkit-parser&lt;/code&gt; 0.18.0&lt;/a&gt; preserves more of the original BGP wire data. Earlier versions parsed many common attributes into typed Rust structures, but element conversion could drop known attributes that did not yet have a semantic parser. That worked for many analysis workflows, but it was not enough for users who need to inspect, serialize, or re-encode less common BGP path attributes.&lt;/p&gt;
&lt;p&gt;This release changes that behavior: the parser now retains known-but-unsupported, malformed, deprecated, and unknown BGP path attributes in &lt;code&gt;AttrRaw&lt;/code&gt;-backed variants instead of silently dropping them.&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo add bgpkit-parser@0.18.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the WebAssembly package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @bgpkit/parser@0.18.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RIS Live raw message parsing&lt;/h2&gt;
&lt;p&gt;RIS Live&apos;s JSON messages expose a convenient projection of UPDATE data, but that projection does not include every BGP path attribute. For example, attributes such as Only to Customer (OTC, code 35), BGP Prefix-SID, BFD Discriminator, and other less common path attributes may appear in the raw UPDATE while remaining absent from the projected JSON fields. Version 0.18.0 adds helpers that parse RIS Live messages containing the original raw BGP UPDATE bytes.&lt;/p&gt;
&lt;p&gt;To request raw BGP messages from RIS Live, set &lt;code&gt;socketOptions.includeRaw&lt;/code&gt; in the subscription message. The crate now re-exports &lt;code&gt;RisSubscribe&lt;/code&gt;, &lt;code&gt;RisSubscribeType&lt;/code&gt;, and &lt;code&gt;RisLiveClientMessage&lt;/code&gt; from the crate root, so examples and downstream applications can build subscription messages directly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::{RisLiveClientMessage, RisSubscribe};

let subscribe = RisSubscribe::new()
    .with_host(&quot;rrc00&quot;)
    .with_include_raw(true);

let msg = RisLiveClientMessage::RisSubscribe(subscribe).to_json_string().unwrap();
// send `msg` over a WebSocket connection to RIS Live
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a RIS Live message arrives, parse the full &lt;code&gt;ris_message&lt;/code&gt; envelope with the raw parser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::parse_ris_live_message_raw;

let elems = parse_ris_live_message_raw(message_text).expect(&quot;parse RIS Live message&quot;);
for elem in elems {
    println!(&quot;{} {} {:?}&quot;, elem.elem_type, elem.prefix, elem.as_path);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Users who prefer RIS Live&apos;s JSON-projected fields can call &lt;code&gt;parse_ris_live_message_json()&lt;/code&gt; for the explicit JSON-path parser, or keep using the older &lt;code&gt;parse_ris_live_message()&lt;/code&gt; function for the same JSON-projected path.&lt;/p&gt;
&lt;h2&gt;Raw BGP attribute retention&lt;/h2&gt;
&lt;p&gt;The main parser now keeps raw bytes for attributes it cannot or should not decode semantically. The new &lt;code&gt;AttributeValue::Raw(AttrRaw)&lt;/code&gt; variant stores the wire attribute code and value bytes for known-but-unsupported attributes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::models::bgp::attributes::AttributeValue;

for attr in attributes.inner.iter() {
    match attr {
        AttributeValue::Raw(raw) =&gt; {
            println!(&quot;raw attribute code={} len={}&quot;, raw.code, raw.bytes.len());
        }
        other =&gt; {
            println!(&quot;typed attribute code={}&quot;, other.attr_code());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This matters for round-trip workflows. If a route contains a known attribute that bgpkit-parser does not yet decode into a typed model, 0.18.0 preserves and re-encodes the original attribute bytes.&lt;/p&gt;
&lt;p&gt;Malformed attributes that target typed parsers now fall back to &lt;code&gt;Raw&lt;/code&gt; after recording a validation warning, rather than disappearing from the parsed attribute list.&lt;/p&gt;
&lt;h2&gt;Deprecated attribute code handling&lt;/h2&gt;
&lt;p&gt;The IANA registry lists attribute code 13 as deprecated: &lt;code&gt;RCID_PATH / CLUSTER_ID&lt;/code&gt;. Version 0.18.0 removes &lt;code&gt;AttrType::CLUSTER_ID&lt;/code&gt; and retains code 13 as deprecated raw data instead.&lt;/p&gt;
&lt;p&gt;If downstream code exhaustively matches &lt;code&gt;AttrType&lt;/code&gt; or &lt;code&gt;AttributeValue&lt;/code&gt;, this is one of the release&apos;s breaking changes. Use &lt;code&gt;AttributeValue::attr_code()&lt;/code&gt; when the raw code point matters, and &lt;code&gt;AttrRaw::attr_type()&lt;/code&gt; when you want to map the raw wire code back to an &lt;code&gt;AttrType&lt;/code&gt; value.&lt;/p&gt;
&lt;h2&gt;New typed BGP path attribute parsers&lt;/h2&gt;
&lt;p&gt;This release adds typed parsers and round-trip encoding support for several RFC-defined path attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7311&quot;&gt;RFC 7311&lt;/a&gt; — AIGP, attribute code 26&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9015&quot;&gt;RFC 9015&lt;/a&gt; — SFP, attribute code 37&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9026&quot;&gt;RFC 9026&lt;/a&gt; — BFD Discriminator, attribute code 38&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8669&quot;&gt;RFC 8669&lt;/a&gt; — BGP Prefix-SID, attribute code 40&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9793&quot;&gt;RFC 9793&lt;/a&gt; — BIER, attribute code 41&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each parser preserves unknown TLVs, so applications can inspect known fields without losing the rest of the attribute payload.&lt;/p&gt;
&lt;p&gt;For example, AIGP exposes an &lt;code&gt;accumulated_metric()&lt;/code&gt; helper:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::models::bgp::attributes::AttributeValue;

for attr in attributes.inner.iter() {
    if let AttributeValue::Aigp(aigp) = attr {
        if let Some(metric) = aigp.accumulated_metric() {
            println!(&quot;AIGP accumulated metric: {metric}&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;API changes to note&lt;/h2&gt;
&lt;p&gt;The main breaking changes affect raw attribute representation and exhaustive matches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AttrRaw&lt;/code&gt; now stores &lt;code&gt;code: u8&lt;/code&gt; instead of &lt;code&gt;attr_type: AttrType&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AttrRaw.bytes&lt;/code&gt; now uses &lt;code&gt;bytes::Bytes&lt;/code&gt; instead of &lt;code&gt;Vec&amp;#x3C;u8&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AigpTlv.value&lt;/code&gt; now uses &lt;code&gt;Bytes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AttributeValue&lt;/code&gt; has new variants: &lt;code&gt;Raw&lt;/code&gt;, &lt;code&gt;BfdDiscriminator&lt;/code&gt;, &lt;code&gt;BgpPrefixSid&lt;/code&gt;, &lt;code&gt;Bier&lt;/code&gt;, and &lt;code&gt;Sfp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The release removes &lt;code&gt;AttrType::CLUSTER_ID&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For most users who iterate over &lt;code&gt;BgpElem&lt;/code&gt;, the visible behavior should stay the same except that the parser preserves more attributes internally. Code that destructures attributes or performs exhaustive matching will need updates.&lt;/p&gt;
&lt;h2&gt;WebAssembly package&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;@bgpkit/parser&lt;/code&gt; npm package also ships 0.18.0. The WASM package continues to expose parsers for BGP UPDATE, BMP/OpenBMP, and MRT data in Node.js, bundler, browser, and Worker environments.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { parseBgpUpdate } from &apos;@bgpkit/parser&apos;;

const elems = parseBgpUpdate(updateBytes);
for (const elem of elems) {
  console.log(elem.type, elem.prefix, elem.as_path);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Rust crate includes RIS Live parsing in this release. We track WASM-level RIS Live parser exports and WebSocket helpers separately.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.18.0&quot;&gt;GitHub release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Crate: &lt;a href=&quot;https://crates.io/crates/bgpkit-parser/0.18.0&quot;&gt;https://crates.io/crates/bgpkit-parser/0.18.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;npm package: &lt;a href=&quot;https://www.npmjs.com/package/@bgpkit/parser/v/0.18.0&quot;&gt;https://www.npmjs.com/package/@bgpkit/parser/v/0.18.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href=&quot;https://docs.rs/bgpkit-parser/0.18.0/bgpkit_parser/&quot;&gt;https://docs.rs/bgpkit-parser/0.18.0/bgpkit_parser/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Finding Well-Known BGP Communities with monocle</title><link>https://bgpkit.com/blog/well-known-bgp-communities-monocle/</link><guid isPermaLink="true">https://bgpkit.com/blog/well-known-bgp-communities-monocle/</guid><description>An ad-hoc, reproducible analysis for scanning public MRT updates for BGP communities, joining them with the IANA registry, and checking behavior against RFC text.</description><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BGP communities are easy to parse and hard to interpret. A route can carry a list of 32-bit values, but the meaning of each value depends on a mix of RFCs, IANA registries, operator conventions, and local routing policy.&lt;/p&gt;
&lt;p&gt;This post walks through a small ad-hoc &lt;a href=&quot;https://bgpkit.com/tools/monocle/&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt; analysis for turning raw MRT updates into a protocol-aware summary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;search public BGP updates for communities in the &lt;code&gt;65535:*&lt;/code&gt; range,&lt;/li&gt;
&lt;li&gt;summarize where each value appears,&lt;/li&gt;
&lt;li&gt;join observed values with the IANA BGP Well-known Communities registry,&lt;/li&gt;
&lt;li&gt;check specific values, such as &lt;code&gt;BLACKHOLE&lt;/code&gt; and &lt;code&gt;NOPEER&lt;/code&gt;, against the relevant RFC text.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The goal is not to infer operator intent from route collector data. This is a one-off research note, not production automation. The goal is narrower: show how BGPKIT tooling can make this kind of evidence gathering easier to repeat and audit.&lt;/p&gt;
&lt;h2&gt;Why &lt;code&gt;65535:*&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc1997&quot;&gt;RFC 1997&lt;/a&gt; introduced the BGP COMMUNITIES path attribute. In the &quot;Well-known Communities&quot; part of the document, it defines several globally significant values and says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The following communities have global significance and their operations shall be implemented in any community-attribute-aware BGP speaker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The current IANA registry is the &lt;a href=&quot;https://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xhtml&quot;&gt;BGP Well-known Communities registry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The registry divides the community space into ranges, including:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Range&lt;/th&gt;&lt;th&gt;Registration rule&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;0x00000000-0x0000FFFF&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Reserved&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;0x00010000-0xFFFEFFFF&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Private Use&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;0xFFFF0000-0xFFFF8000&lt;/code&gt;&lt;/td&gt;&lt;td&gt;First Come First Served&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;0xFFFF8001-0xFFFFFFFF&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Standards Action&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;That makes values with high-order 16 bits set to &lt;code&gt;65535&lt;/code&gt; interesting: they sit in the part of the 32-bit community space where many globally significant values live.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;On macOS, install &lt;code&gt;monocle&lt;/code&gt; with Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install monocle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you prefer building from Rust crates, or are not using Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install monocle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For reproducibility, record the version before running the scan:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This run used:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;monocle 1.3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This analysis used the public MRT index available to &lt;code&gt;monocle&lt;/code&gt; at run time. To make the collector/file set auditable, first write the matching MRT file list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;START=1782324000   # 2026-06-24T18:00:00Z
END=$((START+900)) # 15 minutes
OUT=updates-20260624T1800Z-15m-allcollectors-wk

monocle search \
  --dump-type updates \
  --start-ts $START \
  --end-ts $END \
  --community &apos;65535:*&apos; \
  --broker-files \
  &gt; &quot;$OUT-files.txt&quot;

wc -l &quot;$OUT-files.txt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this run, &lt;code&gt;monocle&lt;/code&gt; found 125 matching MRT update files.&lt;/p&gt;
&lt;p&gt;The exact file count can vary if the public collector index changes or if late files appear. If you need a fixed rerun, save this file list with the analysis artifacts and use the same &lt;code&gt;monocle&lt;/code&gt; version.&lt;/p&gt;
&lt;h2&gt;Searching MRT updates with &lt;code&gt;monocle&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;For this run, I scanned the same 15-minute update window:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;START=1782324000   # 2026-06-24T18:00:00Z
END=$((START+900)) # 15 minutes
OUT=updates-20260624T1800Z-15m-allcollectors-wk

monocle search \
  --dump-type updates \
  --start-ts $START \
  --end-ts $END \
  --community &apos;65535:*&apos; \
  --fields timestamp,collector,prefix,peer_asn,origin_asns,as_path,communities \
  --format json-line \
  &gt; &quot;$OUT.jsonl&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A representative JSONL row looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;timestamp&quot;: 1782324014.12,
  &quot;collector&quot;: &quot;rrc07&quot;,
  &quot;prefix&quot;: &quot;83.171.244.0/24&quot;,
  &quot;peer_asn&quot;: 3399,
  &quot;origin_asns&quot;: [&quot;197761&quot;],
  &quot;as_path&quot;: &quot;3399 5511 6204 51202 51202 197761&quot;,
  &quot;communities&quot;: [&quot;3399:100&quot;, &quot;3399:107&quot;, &quot;5511:500&quot;, &quot;6204:1000&quot;, &quot;65535:65284&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is the community filter:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;--community &apos;65535:*&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;monocle&lt;/code&gt; handles the collector/file discovery and MRT parsing. The output is JSON Lines, so the next step can be a short post-processing script rather than a custom MRT parser.&lt;/p&gt;
&lt;p&gt;For this post, I used small Python scripts rather than building a reusable tool. The main script, &lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/summarize_existing_well_known.py&quot;&gt;&lt;code&gt;summarize_existing_well_known.py&lt;/code&gt;&lt;/a&gt;, groups rows by observed &lt;code&gt;65535:*&lt;/code&gt; value and counts rows, distinct prefixes, collectors, peer ASNs, origin ASNs, address families, and prefix lengths. It also normalizes symbolic community names that &lt;code&gt;monocle&lt;/code&gt; may emit, such as &lt;code&gt;no-export&lt;/code&gt;, back to their numeric well-known-community values before doing the &lt;code&gt;65535:*&lt;/code&gt; grouping.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 summarize_existing_well_known.py &quot;$OUT.jsonl&quot; &quot;$OUT&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second script, &lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/enrich_iana_status.py&quot;&gt;&lt;code&gt;enrich_iana_status.py&lt;/code&gt;&lt;/a&gt;, joins the per-community CSV against a local transcription of the IANA BGP Well-known Communities registry, so each observed value can be labeled as &lt;code&gt;BLACKHOLE&lt;/code&gt;, &lt;code&gt;NOPEER&lt;/code&gt;, &lt;code&gt;GRACEFUL_SHUTDOWN&lt;/code&gt;, &lt;code&gt;Unassigned&lt;/code&gt;, and so on. The registry source is the IANA &lt;a href=&quot;https://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xhtml&quot;&gt;BGP Well-known Communities registry&lt;/a&gt;, checked for this write-up on 2026-06-25.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 enrich_iana_status.py \
  &quot;$OUT-by-community.csv&quot; \
  &quot;$OUT-by-community-iana.csv&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What the scan found&lt;/h2&gt;
&lt;p&gt;In this 15-minute window:&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Matching update rows&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;769,537&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Distinct numeric &lt;code&gt;65535:*&lt;/code&gt; values&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;109&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Values not assigned in the IANA registry snapshot&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;102&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The highest-volume values were:&lt;/p&gt;




































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Community&lt;/th&gt;&lt;th&gt;IANA status&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Rows&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Distinct prefixes&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Collectors&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:6939&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unassigned&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;553,352&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;20&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;27&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:1200&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unassigned&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;63,194&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;10,455&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:34&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unassigned&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;41,584&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;6,022&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;58&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:30&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unassigned&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;37,436&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2,896&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;54&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:65284&lt;/code&gt;&lt;/td&gt;&lt;td&gt;NOPEER, &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3765&quot;&gt;RFC 3765&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;27,766&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,033&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;51&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:9957&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Unassigned&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;13,245&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,125&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:0&lt;/code&gt;&lt;/td&gt;&lt;td&gt;GRACEFUL_SHUTDOWN, &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8326&quot;&gt;RFC 8326&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2,176&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;210&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;51&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;65535:666&lt;/code&gt;&lt;/td&gt;&lt;td&gt;BLACKHOLE, &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999&lt;/a&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,270&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;499&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;18&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This table is a good example of why registry-aware scripting helps. A raw list of communities is just numbers. Once joined with IANA, the same data separates into named RFC-defined values and values that need local/operator-specific interpretation.&lt;/p&gt;
&lt;p&gt;Importantly, &quot;unassigned&quot; does not mean &quot;bad&quot;. Public BGP data alone does not tell us whether a value is a local convention, a route-server policy signal, a legacy convention, an implementation artifact, or something else. It only tells us where the value appeared and gives us concrete rows to inspect next.&lt;/p&gt;
&lt;h2&gt;Checking BLACKHOLE behavior against &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;BLACKHOLE&lt;/code&gt; is assigned as &lt;code&gt;65535:666&lt;/code&gt; by &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two parts of the RFC are useful for a mechanical check.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.2&quot;&gt;RFC 7999 Section 3.2&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A BGP speaker receiving an announcement tagged with the BLACKHOLE community SHOULD add the NO_ADVERTISE or NO_EXPORT community as defined in &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc1997&quot;&gt;RFC 1997&lt;/a&gt;, or a similar community, to prevent propagation of the prefix outside the local AS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.3&quot;&gt;RFC 7999 Section 3.3&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However, blackhole prefix length should be as long as possible in order to limit the impact of discarding traffic for adjacent IP space that is not under DDoS duress. The blackhole prefix length is typically as specific as possible, /32 for IPv4 or /128 for IPv6.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Those sentences translate into simple checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is the prefix &lt;code&gt;/32&lt;/code&gt; for IPv4 or &lt;code&gt;/128&lt;/code&gt; for IPv6?&lt;/li&gt;
&lt;li&gt;does the route also carry &lt;code&gt;NO_EXPORT&lt;/code&gt; or &lt;code&gt;NO_ADVERTISE&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the 15-minute scan, &lt;code&gt;65535:666&lt;/code&gt; appeared in 1,270 rows:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Observed property&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Rows with property&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Rows without property&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/32&lt;/code&gt; or &lt;code&gt;/128&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;474 (37.3%)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;796 (62.7%)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Carries &lt;code&gt;NO_EXPORT&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;460 (36.2%)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;810 (63.8%)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Carries &lt;code&gt;NO_ADVERTISE&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;24 (1.9%)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,246 (98.1%)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Address-family split: 463 IPv4 rows (36.5%) and 807 IPv6 rows (63.5%).&lt;/p&gt;
&lt;p&gt;The resulting rows include examples that match the &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999&lt;/a&gt; profile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;collector&quot;: &quot;decix.fra&quot;,
  &quot;peer_asn&quot;: 6695,
  &quot;prefix&quot;: &quot;193.56.63.113/32&quot;,
  &quot;origin_asns&quot;: [&quot;215514&quot;],
  &quot;as_path&quot;: &quot;201053 50607 215514&quot;,
  &quot;communities&quot;: [&quot;65535:666&quot;, &quot;no-export&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also found examples that lack those observed properties. This is not a compliance judgment: &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.2&quot;&gt;RFC 7999 Section 3.2&lt;/a&gt; describes behavior for a receiving BGP speaker, and collector data may show the route before or after other policy actions. Still, rows like this are useful starting points for follow-up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;collector&quot;: &quot;iix.cgk&quot;,
  &quot;peer_asn&quot;: 7597,
  &quot;prefix&quot;: &quot;2001:4:112::/48&quot;,
  &quot;origin_asns&quot;: [&quot;112&quot;],
  &quot;as_path&quot;: &quot;136106 35280 50628 44097 112&quot;,
  &quot;communities&quot;: [&quot;65535:666&quot;, &quot;7597:1000:1&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is as far as the data should be taken without more context. The collector peer may be a route server. The community may have been attached earlier in the AS path. The value may reflect a local convention. The useful part is that this ad-hoc &lt;code&gt;monocle&lt;/code&gt; analysis can separate these cases quickly and produce concrete rows for follow-up.&lt;/p&gt;
&lt;h2&gt;Checking NOPEER visibility against &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3765&quot;&gt;RFC 3765&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NOPEER&lt;/code&gt; is assigned as &lt;code&gt;65535:65284&lt;/code&gt; by &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3765&quot;&gt;RFC 3765&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3765#section-2&quot;&gt;RFC 3765 Section 2&lt;/a&gt; describes it as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;an advisory qualification to readvertisement of a route prefix, permitting an AS not to readvertise the route prefix to all external bilateral peer neighbour AS&apos;s.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the same 15-minute update window, &lt;code&gt;NOPEER&lt;/code&gt; was broadly visible:&lt;/p&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Rows&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;27,766&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Distinct prefixes&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,033&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Collectors&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;51&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Peer ASNs&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;103&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Origin ASNs&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;146&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;IPv4 rows&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;25,387&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;IPv6 rows&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2,379&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Example row:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;collector&quot;: &quot;rrc07&quot;,
  &quot;peer_asn&quot;: 3399,
  &quot;prefix&quot;: &quot;83.171.244.0/24&quot;,
  &quot;origin_asns&quot;: [&quot;197761&quot;],
  &quot;as_path&quot;: &quot;3399 5511 6204 51202 51202 197761&quot;,
  &quot;communities&quot;: [
    &quot;3399:100&quot;,
    &quot;3399:107&quot;,
    &quot;5511:500&quot;,
    &quot;6204:1000&quot;,
    &quot;65535:65284&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Route data alone does not prove whether any downstream AS enforces the advisory signal. But it does show that the value is operationally present across many collectors, peer ASNs, and origin ASNs in this window.&lt;/p&gt;
&lt;h2&gt;A note on policy behavior&lt;/h2&gt;
&lt;p&gt;It is worth being careful when interpreting well-known communities. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8642&quot;&gt;RFC 8642&lt;/a&gt; exists because well-known community handling has not always been consistent across implementations and policies. Its introduction says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In hindsight, &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc1997&quot;&gt;RFC 1997&lt;/a&gt; did not prescribe as fully as it should have how well-known communities may be manipulated by policies applied by operators. Currently, implementations differ in this regard, and these differences can result in inconsistent behaviors that operators find difficult to identify and resolve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is another reason to keep the analysis evidence-focused. The data can say which routes carried which values at which collectors; it should not assign intent by itself.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;p&gt;A small ad-hoc &lt;a href=&quot;https://bgpkit.com/tools/monocle/&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt; analysis can turn public MRT updates into useful, RFC-aware evidence. The first step is the MRT query; the second step is ordinary JSONL processing that groups rows, counts distinct routing dimensions, and annotates observed values with registry metadata.&lt;/p&gt;
&lt;p&gt;The interesting parts are not hidden inside the MRT files anymore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which &lt;code&gt;65535:*&lt;/code&gt; values are visible,&lt;/li&gt;
&lt;li&gt;whether those values are assigned in the IANA registry,&lt;/li&gt;
&lt;li&gt;where the values appear,&lt;/li&gt;
&lt;li&gt;whether a route matches a simple RFC-derived profile,&lt;/li&gt;
&lt;li&gt;which rows are worth deeper operator-policy investigation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this particular window, the main observations were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;many observed &lt;code&gt;65535:*&lt;/code&gt; values were not assigned in the current IANA well-known-community registry snapshot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BLACKHOLE&lt;/code&gt; appeared both on highly specific prefixes with &lt;code&gt;NO_EXPORT&lt;/code&gt; and on broader prefixes without &lt;code&gt;NO_EXPORT&lt;/code&gt;/&lt;code&gt;NO_ADVERTISE&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NOPEER&lt;/code&gt; was broadly visible across collectors, peers, origins, IPv4, and IPv6.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The broader lesson is the &lt;a href=&quot;https://bgpkit.com/tools/monocle/&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt; research pattern: query a narrow slice of MRT data, summarize it with simple scripts, join with source-of-truth registries, then inspect specific rows with the RFC text next to the data.&lt;/p&gt;
&lt;p&gt;The scripts used for this note are available here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/summarize_existing_well_known.py&quot;&gt;&lt;code&gt;summarize_existing_well_known.py&lt;/code&gt;&lt;/a&gt; — summarize an existing JSONL file;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/enrich_iana_status.py&quot;&gt;&lt;code&gt;enrich_iana_status.py&lt;/code&gt;&lt;/a&gt; — add IANA status labels to the per-community CSV;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/filter_well_known.py&quot;&gt;&lt;code&gt;filter_well_known.py&lt;/code&gt;&lt;/a&gt; — filter generic JSONL output to rows carrying well-known communities;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bgpkit.com/files/well-known-bgp-communities-monocle/summarize_well_known.py&quot;&gt;&lt;code&gt;summarize_well_known.py&lt;/code&gt;&lt;/a&gt; — stream-oriented variant that reads JSONL from stdin.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Investigating the India–Telegram BGP Hijack with monocle</title><link>https://bgpkit.com/blog/investigating-the-india-telegram-bgp-hijack-with-monocle/</link><guid isPermaLink="true">https://bgpkit.com/blog/investigating-the-india-telegram-bgp-hijack-with-monocle/</guid><description>A step-by-step forensic investigation of the June 2026 Telegram BGP hijack using monocle and the BGPKIT API - public data, real commands, real output.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On June 16, 2026, Indian carrier Reliance Communications (AS18101) began originating Telegram&apos;s IP
prefixes in BGP, disrupting access for users inside and briefly outside India. The
event has been covered from a policy angle (&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-prefix-hijack-by-rcom/&quot;&gt;Anurag Bhatia&apos;s analysis&lt;/a&gt;,
&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-bgp-hijack-and-blackholing/&quot;&gt;the IPv6 blackholing follow-up&lt;/a&gt;) and a traffic-impact angle (&lt;a href=&quot;https://www.kentik.com/blog/when-local-blocks-go-global-the-india-telegram-bgp-incident/&quot;&gt;Kentik&apos;s
post&lt;/a&gt;). This post does something different: it shows how to &lt;strong&gt;investigate the
forensic evidence yourself&lt;/strong&gt; using &lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt; and the &lt;a href=&quot;https://api.bgpkit.com&quot;&gt;BGPKIT API&lt;/a&gt; - open-source tools,
real commands, real output. No screenshots of a vendor portal, no proprietary
telemetry. Everything here you can run on your own machine.&lt;/p&gt;
&lt;p&gt;The goal is forensics methodology, not opinion. We won&apos;t speculate about intent or
mitigation policy. We&apos;ll establish ground truth, find the hijack in the data, measure
how it propagated, and check the RPKI timeline - the same steps you&apos;d follow for any
BGP origin-conflict incident.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install monocle  # monocle 1.3.0 used in this post
# cargo install monocle          # also works, but takes longer to build from source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All timestamps below are UTC. &lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt; accepts &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3339&quot;&gt;RFC 3339&lt;/a&gt; strings and converts them
internally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle time &quot;2026-06-16T16:14:19Z&quot; -s
2026-06-16T16:14:19+00:00
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 1: Establish ground truth - who should originate this prefix?&lt;/h2&gt;
&lt;p&gt;Before touching any MRT data, establish the &lt;em&gt;expected&lt;/em&gt; state of the prefix. This is
free and instant, and it gives you the baseline to compare against.&lt;/p&gt;
&lt;p&gt;Telegram operates five ASNs: AS62041, AS62014, AS59930, AS44907, and AS211157. A
single &lt;code&gt;monocle inspect&lt;/code&gt; call pulls all of them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle inspect 62041 62014 59930 44907 211157
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ASN | Name | Country | Org
--- | --- | --- | ---
AS62041 | Telegram Messenger EUR | VG | Telegram Messenger Inc
AS62014 | Telegram Messenger APAC | VG | Telegram Messenger Inc
AS59930 | Telegram Messenger NA  | VG | Telegram Messenger Inc
AS44907 | Telegram Messenger Inc | VG | Telegram Messenger Inc
AS211157| Telegram Messenger Inc | VG | Telegram Messenger Inc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now check who currently announces the prefix that was reported hijacked,
&lt;code&gt;91.105.192.0/23&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle pfx2as 91.105.192.0/23 --show-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The legitimate origin is &lt;strong&gt;AS211157&lt;/strong&gt; (Telegram). The suspect, &lt;strong&gt;AS18101&lt;/strong&gt; (Reliance Communications),
should not appear here in a clean table.&lt;/p&gt;
&lt;h3&gt;The single most important check: RPKI validation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle rpki validate 91.105.192.0/23 18101 --format json
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;prefix&quot;: &quot;91.105.192.0/23&quot;,
  &quot;asn&quot;: 18101,
  &quot;state&quot;: &quot;invalid&quot;,
  &quot;reason&quot;: &quot;ASN 18101 not authorized; authorized ASNs: 211157&quot;,
  &quot;covering_roas&quot;: [
    {&quot;prefix&quot;: &quot;91.105.192.0/23&quot;, &quot;max_length&quot;: 23, &quot;origin_asn&quot;: 211157, &quot;ta&quot;: &quot;RIPENCC&quot;}
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prefix is covered by a ROA authorizing &lt;strong&gt;AS211157&lt;/strong&gt; only. AS18101 originating
it is &lt;strong&gt;RPKI-invalid&lt;/strong&gt; by definition. Any network enforcing &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc6811&quot;&gt;Route Origin Validation
(ROV)&lt;/a&gt; should have rejected this announcement. Confirm the legitimate
origin validates clean:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle rpki validate 91.105.192.0/23 211157 --format json
{&quot;prefix&quot;:&quot;91.105.192.0/23&quot;,&quot;asn&quot;:211157,&quot;state&quot;:&quot;valid&quot;,...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You now know: the prefix is signed, the legitimate origin is valid, and the suspect
origin is invalid - &lt;em&gt;before downloading a single MRT file&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;List Telegram&apos;s signed prefixes&lt;/h3&gt;
&lt;p&gt;To understand what address space was protected by RPKI, pull every Telegram ROA
across all five ASNs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for asn in 62041 62014 59930 44907 211157; do
  monocle rpki roas $asn --format json-line
done | grep &apos;^{&apos; \
  | jq -r &apos;&quot;\(.prefix) AS\(.origin_asn)&quot;&apos; \
  | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This returns 55 ROAs - Telegram&apos;s signed prefix set, from &lt;code&gt;91.105.192.0/23&lt;/code&gt; up
through &lt;code&gt;149.154.160.0/22&lt;/code&gt; and the &lt;code&gt;91.108.x.0/22&lt;/code&gt; blocks. Every one of these
prefixes, if originated by AS18101, would be RPKI-invalid.&lt;/p&gt;
&lt;h3&gt;Understand the suspect&apos;s connectivity&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle as2rel 18101 --is-downstream --show-name  # upstreams
monocle as2rel 18101 15412 --format json | jq &apos;.results[0]&apos;  # relationship with FLAG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reliance&apos;s primary upstreams are &lt;strong&gt;AS15412 (FLAG Telecom)&lt;/strong&gt; and &lt;strong&gt;AS4755 (Tata
Communications)&lt;/strong&gt;. Keep these in mind - they&apos;re the transits that would carry (or
filter) any announcement Reliance originates.&lt;/p&gt;
&lt;h2&gt;Step 2: Find the hijack in the data&lt;/h2&gt;
&lt;p&gt;Now look for the actual BGP announcements. The reported time is 16:14:19 UTC on June
16. Start with a &lt;strong&gt;narrow 5-minute window&lt;/strong&gt; and a &lt;strong&gt;single small collector&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Why a small collector?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;rrc01&lt;/code&gt; (London) and &lt;code&gt;route-views2&lt;/code&gt; are large collectors with hundreds of peers.
Searching them downloads and parses a lot of data. For a first confirmatory search, a
small collector is much faster - and if the event is real, a small collector that has
a peer seeing the path will confirm it just as well.&lt;/p&gt;
&lt;p&gt;Use the &lt;a href=&quot;https://api.bgpkit.com&quot;&gt;BGPKIT API&lt;/a&gt; to find collector peer counts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -s &quot;https://api.bgpkit.com/v3/broker/peers?collector=rrc14&quot; | jq &apos;.data | length&apos;
21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rrc14&lt;/code&gt; (Palo Alto, 21 peers) is small. Let&apos;s search there:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search -p 91.105.192.0/23 -s -S -o 18101 \
  -t 1781626459 -d 5m -c rrc14 \
  --cache-dir ~/.cache/monocle \
  --format json-line \
  | jq -c &apos;.timestamp |= todateiso8601&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;{&quot;timestamp&quot;:&quot;2026-06-16T16:14:49Z&quot;,&quot;type&quot;:&quot;ANNOUNCE&quot;,&quot;prefix&quot;:&quot;91.105.192.0/23&quot;,
 &quot;as_path&quot;:&quot;19151 15412 18101&quot;,&quot;peer_asn&quot;:19151,&quot;collector&quot;:&quot;rrc14&quot;,...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Confirmed. At 16:14:49 UTC, &lt;code&gt;rrc14&lt;/code&gt;&apos;s peer AS19151 received &lt;code&gt;91.105.192.0/23&lt;/code&gt;
originated by AS18101, via the path &lt;code&gt;19151 15412 18101&lt;/code&gt; - meaning &lt;strong&gt;FLAG (AS15412)&lt;/strong&gt;
was the transit carrying the hijack to the global table.&lt;/p&gt;
&lt;h3&gt;Why &lt;code&gt;-s -S&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;-p&lt;/code&gt; prefix filter is &lt;strong&gt;exact-only&lt;/strong&gt; by default. Hijacks routinely involve
more-specifics - the victim announces &lt;code&gt;/24&lt;/code&gt;s to fight back, the hijacker follows with
even more specific routes. A bare &lt;code&gt;-p 91.105.192.0/23&lt;/code&gt; misses both the covering
aggregate and any more-specifics. &lt;code&gt;-s&lt;/code&gt; (include-super) and &lt;code&gt;-S&lt;/code&gt; (include-sub) expand
the match. Get in the habit of adding both to any hijack search.&lt;/p&gt;
&lt;h3&gt;The prefix arms race&lt;/h3&gt;
&lt;p&gt;Widen the search slightly (still one collector, still cached) to see the full set of
prefixes Reliance originated in the escalation wave:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search -p &quot;91.108.4.0/22,149.154.160.0/22,91.105.192.0/23,95.161.64.0/20&quot; \
  -s -S -o 18101 -t 1781594400 -T 1781630400 -c rrc14 \
  --cache-dir ~/.cache/monocle --format json-line \
  | jq -r &apos;&quot;\(.timestamp | todateiso8601) \(.prefix)&quot;&apos; | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;2026-06-16T16:14:49Z  149.154.160.0/24
2026-06-16T16:14:49Z  149.154.161.0/24
2026-06-16T16:14:49Z  149.154.162.0/24
2026-06-16T16:14:49Z  149.154.163.0/24
2026-06-16T16:14:49Z  91.105.192.0/23
2026-06-16T16:14:49Z  91.108.4.0/23
2026-06-16T16:14:49Z  91.108.6.0/23
2026-06-16T16:14:49Z  95.161.64.0/21
2026-06-16T16:14:49Z  95.161.72.0/21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nine prefixes, all announced simultaneously at 16:14:49. Note the specificity: Reliance
originated &lt;code&gt;/23&lt;/code&gt;s &lt;em&gt;under&lt;/em&gt; Telegram&apos;s legitimate &lt;code&gt;/22&lt;/code&gt;s, and &lt;code&gt;/24&lt;/code&gt;s &lt;em&gt;under&lt;/em&gt; the
legitimate &lt;code&gt;/23&lt;/code&gt;s. In BGP, a more-specific route wins. This is the prefix arms race
&lt;a href=&quot;https://www.kentik.com/blog/when-local-blocks-go-global-the-india-telegram-bgp-incident/&quot;&gt;Kentik described visually&lt;/a&gt; - and here it is in the raw update stream, with exact
prefixes and timestamps.&lt;/p&gt;
&lt;h3&gt;Verify every hijacked prefix was RPKI-invalid&lt;/h3&gt;
&lt;p&gt;Validate each against AS18101:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for pf in 91.108.4.0/23 149.154.160.0/24 91.105.192.0/23; do
  monocle rpki validate $pf 18101 --format json-line 2&gt;/dev/null \
    | grep &apos;^{&apos; \
    | jq -r --arg pf &quot;$pf&quot; &apos;&quot;\($pf) → \(.state): \(.reason)&quot;&apos;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;91.108.4.0/23 → invalid: ASN 18101 not authorized; authorized ASNs: 62041
149.154.160.0/24 → invalid: ASN 18101 not authorized; authorized ASNs: 62041
91.105.192.0/23 → invalid: ASN 18101 not authorized; authorized ASNs: 211157
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every hijacked prefix was RPKI-invalid. This is machine-verified evidence, not an
inference. Any transit that carried these was, by definition, not enforcing ROV.&lt;/p&gt;
&lt;h2&gt;Step 3: Was the hijacked route actually selected in the routing table?&lt;/h2&gt;
&lt;p&gt;Announcements in BGP updates tell you what was &lt;em&gt;advertised&lt;/em&gt;. To see what was actually
&lt;em&gt;installed&lt;/em&gt; in a router&apos;s RIB at a given moment, use &lt;code&gt;monocle rib&lt;/code&gt;. This is more
expensive than &lt;code&gt;search&lt;/code&gt; - always pass a collector filter.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle rib 1781627400 -p &quot;91.105.192.0/23,91.108.4.0/22,149.154.160.0/22&quot; \
    -s -S -c rrc14 --format json-line \
    | jq -r &apos;&quot;\(.prefix) origin=AS\(.origin_asns[0]) via \(.as_path)&quot;&apos; | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;149.154.160.0/24 origin=AS18101 via 19151 15412 18101
149.154.161.0/24 origin=AS18101 via 19151 15412 18101
149.154.162.0/24 origin=AS18101 via 19151 15412 18101
149.154.163.0/24 origin=AS18101 via 19151 15412 18101
91.105.192.0/23  origin=AS18101 via 19151 15412 18101
91.108.4.0/23   origin=AS18101 via 19151 15412 18101
91.108.6.0/23   origin=AS18101 via 19151 15412 18101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At 16:30 UTC - sixteen minutes after the hijack began - &lt;code&gt;rrc14&lt;/code&gt;&apos;s peer AS19151 had
&lt;strong&gt;AS18101 installed as the origin&lt;/strong&gt; for all seven prefixes, all via FLAG. The
hijacked route wasn&apos;t just announced; it was the selected route in the RIB.&lt;/p&gt;
&lt;h3&gt;When did it end?&lt;/h3&gt;
&lt;p&gt;Check a later RIB snapshot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle rib 1781683200 -p &quot;91.105.192.0/23,91.108.4.0/22,149.154.160.0/22&quot; \
    -s -S -c rrc14 --format json-line \
    | jq -r &apos;&quot;\(.prefix) origin=AS\(.origin_asns[0]) via \(.as_path)&quot;&apos; | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;91.105.192.0/23 origin=AS211157 via 19151 9002 211157 211157
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By 2026-06-17 08:00 UTC, the prefix is back to origin &lt;strong&gt;AS211157 (Telegram)&lt;/strong&gt; - and
notice the path changed from &lt;code&gt;... 15412 18101&lt;/code&gt; to &lt;code&gt;... 9002 211157&lt;/code&gt;. The prefix is
now reaching this vantage point via AS9002, not FLAG. The hijack was withdrawn
sometime between 20:00 UTC on June 16 and 08:00 UTC on June 17. (&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-prefix-hijack-by-rcom/&quot;&gt;Anurag&apos;s update&lt;/a&gt;
noted the prefix fading behind FLAG around 01:47 IST / 20:17 UTC on June 16.)&lt;/p&gt;
&lt;h2&gt;Step 4: Who propagated the invalid routes?&lt;/h2&gt;
&lt;p&gt;From the RIB and update data, the propagation path at &lt;code&gt;rrc14&lt;/code&gt; is consistently
&lt;code&gt;19151 15412 18101&lt;/code&gt; - &lt;strong&gt;FLAG (AS15412)&lt;/strong&gt; was the transit. Confirm the relationship:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ monocle as2rel 18101 15412 --format json | jq &apos;.results[0]&apos;
{&quot;asn1&quot;:18101,&quot;asn2&quot;:15412,&quot;connected&quot;:&quot;86.5%&quot;,&quot;peer&quot;:&quot;61.6%&quot;,&quot;as2_upstream&quot;:&quot;24.9%&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FLAG is both a peer and an upstream of Reliance at high visibility. Since the
announcement was RPKI-invalid (Step 1) yet FLAG carried it into the global table,
FLAG was not enforcing ROV on this session. The same logic applies to Tata
(AS4755), Reliance&apos;s other transit, which appeared in paths at other collectors.&lt;/p&gt;
&lt;p&gt;Hurricane Electric (AS6939) also appeared briefly in observed AS paths. In the
UPDATE stream, HE propagated the invalid route for about 40 seconds and then
withdrew it, consistent with an RPKI-invalid rejection path that was slower than
the initial propagation. That is useful timing evidence, but it should not be read
as a statement about traffic volume or broad customer impact.&lt;/p&gt;
&lt;h2&gt;Step 5: The RPKI timeline - did Telegram have ROAs before the incident?&lt;/h2&gt;
&lt;p&gt;A common pattern in BGP hijack history is &lt;em&gt;reactive&lt;/em&gt; ROA creation: &lt;a href=&quot;https://manrs.org/2021/02/did-someone-try-to-hijack-twitter-yes/&quot;&gt;Twitter created
ROAs for its routes after the 2021 Myanmar hijack&lt;/a&gt;, and that protection limited
damage during the &lt;a href=&quot;https://manrs.org/2022/03/lesson-learned-twitter-shored-up-its-routing-security/&quot;&gt;2022 Russia incident&lt;/a&gt;. Was Telegram in the same reactive position,
or did it already have ROAs?&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://alpha.api.bgpkit.com&quot;&gt;BGPKIT Alpha API&lt;/a&gt; provides historical ROA data with &lt;code&gt;date_ranges&lt;/code&gt; arrays showing
when each ROA was active:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ curl -s &quot;https://alpha.api.bgpkit.com/roas?asn=211157&amp;#x26;date=2026-06-16&quot; \
    | jq &apos;.data[] | {prefix, date_ranges}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;prefix&quot;: &quot;91.105.192.0/23&quot;,
  &quot;date_ranges&quot;: [
    [&quot;2023-09-23&quot;, &quot;2025-05-04&quot;],
    [&quot;2025-05-06&quot;, &quot;2026-06-12&quot;],
    [&quot;2026-06-14&quot;, &quot;2026-06-20&quot;]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Telegram&apos;s ROA for &lt;code&gt;91.105.192.0/23&lt;/code&gt; dates back to &lt;strong&gt;September 2023&lt;/strong&gt; - nearly three
years before this incident. Unlike &lt;a href=&quot;https://manrs.org/2021/02/did-someone-try-to-hijack-twitter-yes/&quot;&gt;Twitter&apos;s reactive adoption&lt;/a&gt;, Telegram had
protection in place well ahead of time. This is why RPKI-RoV was able to limit the
hijack&apos;s global impact (&lt;a href=&quot;https://www.kentik.com/blog/when-local-blocks-go-global-the-india-telegram-bgp-incident/&quot;&gt;Kentik reported&lt;/a&gt; only ~1.6% of their BGP sources saw the
hijacked route). The protection was already there; the question was whether
transits enforced it.&lt;/p&gt;
&lt;h2&gt;Step 6: What about the IPv6 blackholing story?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-bgp-hijack-and-blackholing/&quot;&gt;Anurag&apos;s second post&lt;/a&gt; describes a separate phenomenon: Indian ISPs (TTSL AS45820,
Lightstorm AS152144/AS135709) originating Telegram&apos;s IPv6 prefix &lt;code&gt;2a0a:f280::/48&lt;/code&gt;
and its aggregate &lt;code&gt;2a0a:f280::/32&lt;/code&gt;, which he hypothesizes is a misapplied
remote-triggered blackhole configuration.&lt;/p&gt;
&lt;p&gt;For this post, the important question is not whether the &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999 BLACKHOLE
community&lt;/a&gt; (&lt;code&gt;65535:666&lt;/code&gt;) appears. A standards-compliant blackhole route
should be tightly scoped: &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.2&quot;&gt;section 3.2&lt;/a&gt; says receivers should
add NO_ADVERTISE or NO_EXPORT, and &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.3&quot;&gt;section 3.3&lt;/a&gt; says the
neighboring network must be authorized to advertise the covering prefix. TTSL and
Lightstorm are not authorized for Telegram&apos;s IPv6 space, so the observable BGP
fact remains the same either way: these are unauthorized origins.&lt;/p&gt;
&lt;p&gt;Bryton Herdes&apos; &lt;a href=&quot;https://www.youtube.com/watch?v=XDt9mx9z3A4&quot;&gt;False immunity: long prefixes that bypass ROV&lt;/a&gt;
talk is useful background on how TE and RTBH exceptions can create loose handling
for long prefixes. But the public BGP evidence here cannot prove the operators&apos;
internal mechanism; it can only show the invalid origins.&lt;/p&gt;
&lt;p&gt;What is observable is that these ISPs are originating prefixes they do not own -
RPKI-invalid for those origins. This is the same routing anomaly pattern as the
Reliance Communications hijack, regardless of the mechanism. You can verify it the same way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle pfx2as 2a0a:f280::/48 --show-name          # who announces it?
monocle rpki validate 2a0a:f280::/48 45820 --format json  # is TTSL authorized?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A secondary puzzle remains: if this is not standard &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999&lt;/a&gt; RTBH, what mechanism
caused these ISPs to originate Telegram&apos;s IPv6 prefixes? That question is worth
investigating, but it is not something BGP UPDATE data alone can answer - it
requires off-band information about the operators&apos; configuration practices.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Here is the complete evidence chain, reproducible from the commands above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ground truth:&lt;/strong&gt; &lt;code&gt;91.105.192.0/23&lt;/code&gt; is RPKI-signed for AS211157 (Telegram).
AS18101 (Reliance Communications) originating it is RPKI-invalid. (&lt;code&gt;monocle rpki validate&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The hijack:&lt;/strong&gt; At 16:14:49 UTC on June 16, Reliance originated 9 Telegram prefixes
(&lt;code&gt;/23&lt;/code&gt;s and &lt;code&gt;/24&lt;/code&gt;s under the legitimate aggregates), all via FLAG (AS15412).
(&lt;code&gt;monocle search -s -S -c rrc14&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RIB confirmation:&lt;/strong&gt; At 16:30 UTC, the hijacked routes were installed in
&lt;code&gt;rrc14&lt;/code&gt;&apos;s RIB with AS18101 as origin. By 08:00 UTC on June 17, the prefix was
back to AS211157. (&lt;code&gt;monocle rib -c rrc14&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROV failure:&lt;/strong&gt; FLAG (AS15412) and Tata (AS4755) carried RPKI-invalid
announcements, so neither enforced ROV on those sessions. (&lt;code&gt;monocle as2rel&lt;/code&gt; +
propagation data)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROA timeline:&lt;/strong&gt; Telegram&apos;s ROAs date to September 2023 - protection was
proactive, not reactive. (&lt;a href=&quot;https://alpha.api.bgpkit.com&quot;&gt;BGPKIT Alpha API&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPv6 anomaly:&lt;/strong&gt; TTSL and Lightstorm originated Telegram&apos;s IPv6 prefixes
(&lt;code&gt;2a0a:f280::/48&lt;/code&gt; and aggregate &lt;code&gt;2a0a:f280::/32&lt;/code&gt;), RPKI-invalid for those
origins. &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.3&quot;&gt;RFC 7999 section 3.3&lt;/a&gt; describes blackhole prefixes as &quot;typically as
specific as possible, /32 for IPv4 or /128 for IPv6,&quot; and &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999#section-3.3&quot;&gt;section 3.3&lt;/a&gt;&apos;s MUST
conditions require the origin to be authorized - neither fits aggregates
originated by an unauthorized AS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Every command in this post has been run against live public data. That&apos;s the point:
BGP forensics doesn&apos;t require a proprietary platform. &lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;&lt;code&gt;monocle&lt;/code&gt;&lt;/a&gt;, the &lt;a href=&quot;https://api.bgpkit.com&quot;&gt;BGPKIT API&lt;/a&gt;, and
public MRT archives are sufficient to investigate - and verify - any routing
incident.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-prefix-hijack-by-rcom/&quot;&gt;Anurag Bhatia - Telegram prefixes hijack by Rcom AS18101&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://anuragbhatia.com/post/2026/06/telegram-bgp-hijack-and-blackholing/&quot;&gt;Anurag Bhatia - Telegram BGP hijack due to weird blackholing config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kentik.com/blog/when-local-blocks-go-global-the-india-telegram-bgp-incident/&quot;&gt;Kentik - When Local Blocks Go Global: The India Telegram BGP Incident&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7999&quot;&gt;RFC 7999 - BLACKHOLE Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc6811&quot;&gt;RFC 6811 - BGP Prefix Origin Validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3339&quot;&gt;RFC 3339 - Date and Time on the Internet: Timestamps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;monocle on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api.bgpkit.com&quot;&gt;BGPKIT API&lt;/a&gt; · &lt;a href=&quot;https://alpha.api.bgpkit.com&quot;&gt;BGPKIT Alpha API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://manrs.org/2021/02/did-someone-try-to-hijack-twitter-yes/&quot;&gt;MANRS - Did someone try to hijack Twitter? Yes!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://manrs.org/2022/03/lesson-learned-twitter-shored-up-its-routing-security/&quot;&gt;MANRS - Lesson Learned: Twitter Shored Up Its Routing Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=XDt9mx9z3A4&quot;&gt;Bryton Herdes - False immunity: long prefixes that bypass ROV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>monocle v1.3.0: Reconstruct BGP RIB State at Any Timestamp</title><link>https://bgpkit.com/blog/monocle-v1-3-0/</link><guid isPermaLink="true">https://bgpkit.com/blog/monocle-v1-3-0/</guid><description>monocle v1.3.0 introduces the rib command for reconstructing BGP RIB state at arbitrary timestamps.</description><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are happy to announce the release of &lt;strong&gt;monocle v1.3.0&lt;/strong&gt;, now available on &lt;a href=&quot;https://github.com/bgpkit/monocle/releases/tag/v1.3.0&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://crates.io/crates/monocle/1.3.0&quot;&gt;crates.io&lt;/a&gt;, also available on Homebrew via &lt;code&gt;brew install monocle&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This release introduces a major new command, &lt;code&gt;monocle rib&lt;/code&gt;, which reconstructs BGP Routing Information Base (RIB) state at arbitrary timestamps.&lt;/p&gt;
&lt;h2&gt;New Feature: &lt;code&gt;monocle rib&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Most BGP tools show you what happened &lt;em&gt;during&lt;/em&gt; an update file. &lt;code&gt;monocle rib&lt;/code&gt; shows you the routing table &lt;em&gt;as it existed&lt;/em&gt; at any moment in time.&lt;/p&gt;
&lt;p&gt;Given a target timestamp, the command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Finds the latest RIB dump at or before that time&lt;/li&gt;
&lt;li&gt;Downloads and replays all overlapping BGP update files up to the exact target timestamp&lt;/li&gt;
&lt;li&gt;Materializes the final route state per peer and prefix&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result is the reconstructed RIB — not raw BGP messages, but the actual route state.&lt;/p&gt;
&lt;h3&gt;Basic Usage&lt;/h3&gt;
&lt;p&gt;Reconstruct the RIB for a single timestamp and print to stdout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle rib 2025-09-01T12:00:00Z -c route-views6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write multiple snapshots to a single SQLite file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle rib \
  2025-09-01T08:00:00Z \
  2025-09-01T12:00:00Z \
  --sqlite-path /tmp/rib-snapshots.sqlite3 \
  -c route-views6
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Filtering&lt;/h3&gt;
&lt;p&gt;Apply filters at reconstruction time to narrow the result set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Only routes originated by AS13335 (Cloudflare)
monocle rib 2025-09-01T12:00:00Z -c route-views6 -o 13335

# Routes for a specific prefix, including more-specifics
monocle rib 2025-09-01T12:00:00Z -c route-views6 -p 2606:4700::/32 --include-sub

# Routes from full-feed peers only
monocle rib 2025-09-01T12:00:00Z -c route-views6 --full-feed-only

# Routes where AS path contains a specific ASN
monocle rib 2025-09-01T12:00:00Z -c route-views6 -a &quot;13335&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Performance Notes&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;rib&lt;/code&gt; command keeps the working RIB state in memory during reconstruction, avoiding SQLite lookups and writes on the hot path. The updates query window was tightened from +2 hours lookahead to the exact target timestamp, reducing update file downloads by roughly one third for typical requests.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install monocle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or download pre-built binaries for Linux (x86_64, aarch64) and macOS (universal) from the &lt;a href=&quot;https://github.com/bgpkit/monocle/releases/tag/v1.3.0&quot;&gt;GitHub release page&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>monocle v1.1.0</title><link>https://bgpkit.com/blog/monocle-v110/</link><guid isPermaLink="true">https://bgpkit.com/blog/monocle-v110/</guid><description>monocle v1.1.0 focuses on interface consistency and day-to-day usability. This release simplifies feature gates, standardizes data refresh APIs, and adds qualit...</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;monocle &lt;code&gt;v1.1.0&lt;/code&gt; focuses on interface consistency and day-to-day usability. This release simplifies feature gates, standardizes data refresh APIs, and adds quality-of-life improvements across parsing, search, and configuration workflows.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Feature flags are now simplified to &lt;code&gt;lib&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;, and &lt;code&gt;cli&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Config and update flows are more consistent (&lt;code&gt;config update&lt;/code&gt;, &lt;code&gt;config backup&lt;/code&gt;, &lt;code&gt;config sources&lt;/code&gt;, and &lt;code&gt;--no-update&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data refresh APIs are standardized across ASInfo, AS2Rel, RPKI, and Pfx2as.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cache TTL defaults are unified at 7 days, with clearer staleness reporting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parse/search workflows gain multi-value filters, negation filters, field selection, ordering, timestamp format control, and local cache support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What&apos;s New&lt;/h2&gt;
&lt;h3&gt;Simpler feature flags&lt;/h3&gt;
&lt;p&gt;The crate feature model is now reduced to three options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt;: complete library functionality (database + lenses + display)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;server&lt;/code&gt;: WebSocket server support (implies &lt;code&gt;lib&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cli&lt;/code&gt;: full command-line binary (implies &lt;code&gt;lib&lt;/code&gt; and &lt;code&gt;server&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This replaces the previous multi-tier setup and makes dependency selection easier for downstream users.&lt;/p&gt;
&lt;h3&gt;Standardized data refresh APIs&lt;/h3&gt;
&lt;p&gt;Database refresh behavior is now more uniform across ASInfo, AS2Rel, RPKI, and Pfx2as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Consistent &lt;code&gt;needs_*_refresh(ttl)&lt;/code&gt; checks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A shared &lt;code&gt;RefreshResult&lt;/code&gt; shape with source and load details&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Standardized naming (&lt;code&gt;refresh_*&lt;/code&gt;) with compatibility aliases where needed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;URL and local-path loading paths available across repositories&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This update reduces API drift and makes maintenance code paths more predictable.&lt;/p&gt;
&lt;h3&gt;Config command updates&lt;/h3&gt;
&lt;p&gt;Configuration and maintenance commands now use clearer naming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;monocle config db-refresh&lt;/code&gt; -&gt; &lt;code&gt;monocle config update&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;monocle config db-backup&lt;/code&gt; -&gt; &lt;code&gt;monocle config backup&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;monocle config db-sources&lt;/code&gt; -&gt; &lt;code&gt;monocle config sources&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The global no-refresh toggle was also renamed for consistency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--no-refresh&lt;/code&gt; -&gt; &lt;code&gt;--no-update&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following command shows the active configuration, cache TTL settings, database status, and server defaults:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;monocle Configuration
=====================

General:
  Config file:    /home/user/.monocle/monocle.toml
  Data dir:       /home/user/.monocle/

Cache TTL:
  ASInfo:         7 days
  AS2Rel:         7 days
  RPKI:           7 days
  Pfx2as:         7 days

Database:
  Path:           /home/user/.monocle/monocle-data.sqlite3
  Status:         exists
  Size:           512.47 MB
  Schema:         initialized (v3)
  ASInfo:         120953 records (updated: 2026-02-02 19:54:01 UTC)
  AS2Rel:         877937 records (updated: 2026-02-02 14:25:34 UTC)
  RPKI:           796899 ROAs, 962 ASPAs (updated: 2026-02-10 19:53:50 UTC)
  Pfx2as:         1580626 records (updated: 2026-02-02 20:02:10 UTC)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Better cache control defaults&lt;/h3&gt;
&lt;p&gt;All major data sources now support configurable cache TTL with a 7-day default. This applies to ASInfo, AS2Rel, RPKI, and Pfx2as.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;monocle config sources&lt;/code&gt; now reports staleness based on TTL, so it is easier to see what needs updating.&lt;/p&gt;
&lt;p&gt;The following command shows per-source status, staleness, and last update recency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle config sources
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Data Sources:

  Name         Status          Stale      Last Updated
  ------------------------------------------------------------
  asinfo       120953 records  yes        a week ago
  as2rel       877937 records  yes        a week ago
  rpki         797861 records  no         2 hours ago
  pfx2as       1580626 records yes        a week ago

Configuration:
  ASInfo cache TTL: 7 days
  AS2Rel cache TTL: 7 days
  RPKI cache TTL:   7 days
  Pfx2as cache TTL: 7 days
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RPKI improvements&lt;/h3&gt;
&lt;p&gt;monocle now supports fetching ROAs via RTR (RPKI-to-Router), including endpoint override support and fallback behavior.&lt;/p&gt;
&lt;p&gt;The following command refreshes only RPKI data and uses the provided RTR endpoint for this run instead of the default configured source.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle config update --rpki --rtr-endpoint rtr.rpki.cloudflare.com:8282
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Parse and search enhancements&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;parse&lt;/code&gt; and &lt;code&gt;search&lt;/code&gt; gained several output and filtering improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Multi-value filters with OR semantics&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Negation filters using &lt;code&gt;!&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validation for ASN/prefix filter inputs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--fields&lt;/code&gt; for column selection&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--order-by&lt;/code&gt; and &lt;code&gt;--order&lt;/code&gt; for sorted output&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--time-format&lt;/code&gt; for unix or RFC3339 display&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;search --cache-dir&lt;/code&gt; local file + broker query caching&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following command searches one hour of updates starting at &lt;code&gt;2024-01-01&lt;/code&gt;, filters for prefix &lt;code&gt;1.1.1.0/24&lt;/code&gt;, and caches downloaded MRT files plus broker query results under &lt;code&gt;/tmp/mrt-cache&lt;/code&gt; for faster repeat runs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search -t 2024-01-01 -d 1h -p 1.1.1.0/24 --cache-dir /tmp/mrt-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following command uses multi-value filters with negation to exclude two origin ASNs while also matching either of two peer ASNs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;monocle search -t 2024-01-01 -d 1h -o &apos;!13335,!15169&apos; -J 174,2914
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Negation and positive values cannot be mixed within the same filter field.&lt;/p&gt;
&lt;h2&gt;Breaking Changes and Migration Notes&lt;/h2&gt;
&lt;h3&gt;1) Feature flag migration&lt;/h3&gt;
&lt;p&gt;If you previously used feature tiers like &lt;code&gt;database&lt;/code&gt;, &lt;code&gt;lens-core&lt;/code&gt;, &lt;code&gt;lens-bgpkit&lt;/code&gt;, &lt;code&gt;lens-full&lt;/code&gt;, or &lt;code&gt;display&lt;/code&gt;, switch to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;lib&lt;/code&gt; for library use&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;server&lt;/code&gt; for WebSocket API use&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cli&lt;/code&gt; for full command-line use&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2) CLI and subcommand renames&lt;/h3&gt;
&lt;p&gt;Update scripts and automation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--no-refresh&lt;/code&gt; -&gt; &lt;code&gt;--no-update&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config db-refresh&lt;/code&gt; -&gt; &lt;code&gt;config update&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config db-backup&lt;/code&gt; -&gt; &lt;code&gt;config backup&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;config db-sources&lt;/code&gt; -&gt; &lt;code&gt;config sources&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3) Parse/search filter type updates (library API)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ParseFilters&lt;/code&gt; moved from scalar optional fields to vector-based values for multi-value and negation support. Library consumers should update filter construction accordingly.&lt;/p&gt;
&lt;h2&gt;Additional Improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AS name rendering now prefers PeeringDB naming fields before falling back to AS2Org/core names&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data refresh logging now shows specific reasons (empty vs outdated)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Example layout was reorganized to one example per lens&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Full Change List&lt;/h2&gt;
&lt;p&gt;See the v1.1.0 section in &lt;a href=&quot;https://github.com/bgpkit/monocle/blob/main/CHANGELOG.md&quot;&gt;&lt;code&gt;CHANGELOG.md&lt;/code&gt;&lt;/a&gt; for the complete list of changes.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>BGPKIT Parser v0.14.0 Release and v0.13.0 Highlights</title><link>https://bgpkit.com/blog/bgpkit-parser-v0140-release/</link><guid isPermaLink="true">https://bgpkit.com/blog/bgpkit-parser-v0140-release/</guid><description>We are pleased to announce the release of BGPKIT Parser v0.14.0. This update introduces support for negative filters and the RPKI-to-Router (RTR) protocol. We a...</description><pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are pleased to announce the release of &lt;strong&gt;BGPKIT Parser v0.14.0&lt;/strong&gt;. This update introduces support for negative filters and the RPKI-to-Router (RTR) protocol. We also want to highlight key features from the recent v0.13.0 release, including enhanced debugging tools.&lt;/p&gt;
&lt;h2&gt;v0.14.0 Features&lt;/h2&gt;
&lt;h3&gt;Negative Filter Support&lt;/h3&gt;
&lt;p&gt;A frequent request has been the ability to filter &lt;em&gt;out&lt;/em&gt; specific data points. We have added support for negative filters across most filter types, allowing exclusion of specific origins, prefixes, peers, or communities.&lt;/p&gt;
&lt;p&gt;In the CLI, use the &lt;code&gt;!=&lt;/code&gt; operator. For example, to process all records &lt;em&gt;except&lt;/em&gt; those originating from AS 13335:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bgpkit-parser https://spaces.bgpkit.org/parser/update-example.gz --filter &quot;origin_asn!=13335&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/negative-filter.C1GKM1D4.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;In Rust code, use the &lt;code&gt;!&lt;/code&gt; prefix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let parser = BgpkitParser::new(&quot;...&quot;)
    .add_filter(&quot;!origin_asn&quot;, &quot;13335&quot;)
    .add_filter(&quot;!peer_ip&quot;, &quot;192.168.1.1&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Supported negative filters include &lt;code&gt;!origin_asn&lt;/code&gt;, &lt;code&gt;!prefix&lt;/code&gt;, &lt;code&gt;!peer_ip&lt;/code&gt;, &lt;code&gt;!peer_asn&lt;/code&gt;, &lt;code&gt;!type&lt;/code&gt;, &lt;code&gt;!as_path&lt;/code&gt;, &lt;code&gt;!community&lt;/code&gt;, and &lt;code&gt;!ip_version&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;RPKI RTR Protocol Support&lt;/h3&gt;
&lt;p&gt;We have added support for the RPKI-to-Router (RTR) protocol, covering both version 0 (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6810&quot;&gt;RFC 6810&lt;/a&gt;) and version 1 (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8210&quot;&gt;RFC 8210&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;models::rpki::rtr&lt;/code&gt; and &lt;code&gt;parser::rpki::rtr&lt;/code&gt; modules allow developers to build custom RTR clients or servers.&lt;/p&gt;
&lt;p&gt;Here is how you can use the library to connect to an RTR server and request data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::models::rpki::rtr::*;
use bgpkit_parser::parser::rpki::rtr::{read_rtr_pdu, RtrEncode, RtrError};
use std::net::TcpStream;
use std::io::Write;

// 1. Connect to the RTR server
let mut stream = TcpStream::connect(&quot;rtr.rpki.cloudflare.com:8282&quot;)?;

// 2. Send a Reset Query to request the full database
let reset_query = RtrResetQuery::new_v1();
stream.write_all(&amp;#x26;reset_query.encode())?;

// 3. Read the response PDUs
loop {
    match read_rtr_pdu(&amp;#x26;mut stream)? {
        RtrPdu::IPv4Prefix(p) =&gt; {
            println!(&quot;Received IPv4 ROA: {}/{}-{} -&gt; AS{}&quot;, 
                p.prefix, p.prefix_length, p.max_length, p.asn);
        }
        RtrPdu::EndOfData(_) =&gt; break,
        _ =&gt; {}
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have included a fully functional RTR client example that connects to a server, fetches ROAs, and performs route validation.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/rtr-client.D_ecek_F.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;You can run the example yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo run --example rtr_client -- rtr.rpki.cloudflare.com 8282
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;In Case You Missed It: v0.13.0&lt;/h2&gt;
&lt;p&gt;The v0.13.0 release introduced several improvements for debugging and analyzing MRT data.&lt;/p&gt;
&lt;h3&gt;Record-Level Output&lt;/h3&gt;
&lt;p&gt;The CLI supports inspecting individual MRT records rather than just parsed BGP elements. This aids in debugging parser issues or analyzing raw MRT files.&lt;/p&gt;
&lt;p&gt;Switch to record-level output with &lt;code&gt;--level records&lt;/code&gt; and choose a format (e.g., JSON):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bgpkit-parser https://spaces.bgpkit.org/parser/update-example.gz --level records --format json
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/record-level-output.BbWoaz9O.png&quot; alt=&quot;&quot;&gt;
&lt;h3&gt;Raw Bytes Access&lt;/h3&gt;
&lt;p&gt;For developers, &lt;code&gt;RawMrtRecord&lt;/code&gt; now includes a &lt;code&gt;header_bytes&lt;/code&gt; field, and the &lt;code&gt;raw_bytes&lt;/code&gt; field has been renamed to &lt;code&gt;message_bytes&lt;/code&gt;. This provides access to the exact bytes of the MRT header and the message body as they appeared on the wire, enabling byte-for-byte export and debugging without re-encoding.&lt;/p&gt;
&lt;h3&gt;Other Improvements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Testing &amp;#x26; Fuzzing&lt;/strong&gt;: Added a &lt;code&gt;cargo-fuzz&lt;/code&gt; harness and initial fuzz targets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Continued optimizations for faster processing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the full &lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/blob/main/CHANGELOG.md&quot;&gt;CHANGELOG&lt;/a&gt; for more details.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Happy Parsing!&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>BGPKIT Broker v0.9.0: Better Pagination and New Collectors</title><link>https://bgpkit.com/blog/bgpkit-broker-v090/</link><guid isPermaLink="true">https://bgpkit.com/blog/bgpkit-broker-v090/</guid><description>We are excited to announce the release of BGPKIT Broker v0.9.0. This release introduces total count support for efficient pagination, making it easier to build ...</description><pubDate>Sat, 01 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We are excited to announce the release of BGPKIT Broker v0.9.0. This release introduces total count support for efficient pagination, making it easier to build data exploration interfaces and manage large result sets. We also expand our collector coverage with two new Internet Exchange points.&lt;/p&gt;
&lt;h2&gt;Previously on BGPKIT Broker&lt;/h2&gt;
&lt;p&gt;Since the major v0.7 release that unified the architecture into a single SQLite-backed CLI application, BGPKIT Broker has been serving the community with stable uptime and continuous data coverage. The v0.8 series brought several enhancements including automated backup systems, configuration validation, and convenient query shortcuts.&lt;/p&gt;
&lt;p&gt;However, one common challenge remained for developers building interfaces on top of Broker: pagination. When querying large time ranges, users needed to fetch all results to know the total count, making it inefficient to build proper pagination controls or display result statistics.&lt;/p&gt;
&lt;h2&gt;V0.9: Efficient Pagination Support&lt;/h2&gt;
&lt;p&gt;Version 0.9.0 addresses pagination needs with total count support at both the SDK and API levels. This allows applications to fetch result counts independently from the data itself, enabling responsive user interfaces without over-fetching data.&lt;/p&gt;
&lt;h3&gt;SDK: &lt;code&gt;query_total_count()&lt;/code&gt; Method&lt;/h3&gt;
&lt;p&gt;The SDK now provides a dedicated &lt;code&gt;query_total_count()&lt;/code&gt; method that returns the total number of matching items without retrieving the actual data. This is useful when you need to know how many results exist before deciding how to paginate through them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_broker::BgpkitBroker;

#[tokio::main]
async fn main() {
    let broker = BgpkitBroker::new()
        .ts_start(&quot;2024-10-01&quot;)
        .ts_end(&quot;2024-10-02&quot;)
        .project(&quot;route-views&quot;);

    // Get total count without fetching items
    let total = broker.query_total_count().await.unwrap();
    println!(&quot;Total matching items: {}&quot;, total);

    // Now fetch paginated results
    let result = broker
        .page_size(100)
        .page(1)
        .query()
        .await
        .unwrap();

    println!(&quot;Retrieved {} items out of {} total&quot;, 
             result.data.len(), 
             result.total.unwrap_or(0));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method executes a &lt;code&gt;COUNT(*)&lt;/code&gt; query against the database with the same filters as your main query, returning just the number rather than all the data. For large time ranges with thousands of results, this can save significant bandwidth and processing time.&lt;/p&gt;
&lt;h3&gt;API: Total Field in Search Results&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;/v3/search&lt;/code&gt; endpoint now includes a &lt;code&gt;total&lt;/code&gt; field in every response, providing the total count of matching items alongside the paginated results. This enhancement makes it straightforward to build pagination controls in web interfaces or CLI tools.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl &quot;https://api.bgpkit.com/v3/broker/search?ts_start=2024-10-01&amp;#x26;ts_end=2024-10-02&amp;#x26;page_size=10&amp;#x26;page=1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;total&quot;: 1234,
  &quot;data&quot;: [
    {
      &quot;ts_start&quot;: 1727740800,
      &quot;ts_end&quot;: 1727740800,
      &quot;collector&quot;: &quot;route-views2&quot;,
      &quot;project&quot;: &quot;route-views&quot;,
      &quot;url&quot;: &quot;...&quot;,
      ...
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;total&lt;/code&gt; field, client applications can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Display &quot;showing X of Y results&quot; to users&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calculate the number of pages needed for pagination&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decide whether to fetch all results or paginate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provide accurate progress indicators for data processing&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;New RouteViews Collectors&lt;/h3&gt;
&lt;p&gt;This release adds support for two new RouteViews collectors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;hkix.hkg&lt;/strong&gt;: Hong Kong Internet Exchange (HKIX) collector in Hong Kong&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ix-br.gru&lt;/strong&gt;: &lt;a href=&quot;https://IX.br&quot;&gt;IX.br&lt;/a&gt; (&lt;a href=&quot;https://PTT.br&quot;&gt;PTT.br&lt;/a&gt;) in São Paulo&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These additions enhance BGPKIT Broker&apos;s global coverage, providing more diverse vantage points into internet routing behavior. Users who update to v0.9.0 can run &lt;code&gt;bgpkit-broker update&lt;/code&gt; to automatically bootstrap data for these new collectors.&lt;/p&gt;
&lt;h2&gt;Behind the Scenes Improvements&lt;/h2&gt;
&lt;p&gt;Beyond the user-facing features, we also made several code improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Refactored bootstrap download logging to centralize progress reporting and eliminate redundant code paths&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Updated the oneio dependency to v0.20.0 with optimized feature flags for better handling rustls providers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhanced test coverage overall&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Upgrading to v0.9.0&lt;/h2&gt;
&lt;h3&gt;For SDK Users&lt;/h3&gt;
&lt;p&gt;Update your &lt;code&gt;Cargo.toml&lt;/code&gt; to use the latest v0.9 release:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[dependencies]
bgpkit-broker = &quot;0.9&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new &lt;code&gt;query_total_count()&lt;/code&gt; method is immediately available for use. The &lt;code&gt;total&lt;/code&gt; field in &lt;code&gt;BrokerQueryResult&lt;/code&gt; is backward compatible as an optional field.&lt;/p&gt;
&lt;h3&gt;For Self-Hosted Instances&lt;/h3&gt;
&lt;p&gt;If you run your own BGPKIT Broker instance, update to the latest version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install --force bgpkit-broker --version 0.9.0 --features cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or pull the latest Docker image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker pull bgpkit/bgpkit-broker:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After upgrading, run the update command to add the new collectors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bgpkit-broker update --db-path your-database.sqlite3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The update process will automatically detect and bootstrap historical data for the two new collectors.&lt;/p&gt;
&lt;h2&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;BGPKIT Broker continues to serve as a fundamental component for BGP data processing pipelines. With v0.9.0, we focused on making the developer experience better for building applications that need pagination and result statistics. We continue to maintain the public Broker instance at &lt;a href=&quot;https://api.bgpkit.com/v3/broker&quot;&gt;&lt;code&gt;https://api.bgpkit.com/v3/broker&lt;/code&gt;&lt;/a&gt; with 99.996% uptime, and encourage users to deploy on-premise instances for production pipelines to reduce external dependencies.&lt;/p&gt;
&lt;p&gt;For the full v0.9.0 release notes, please check out our &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker/releases/tag/v0.9.0&quot;&gt;GitHub release page&lt;/a&gt;. If you have any comments, please drop us a message on &lt;a href=&quot;https://twitter.com/bgpkit&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://infosec.exchange/@bgpkit&quot;&gt;Mastodon&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/bgpkit.com&quot;&gt;Bluesky&lt;/a&gt; or &lt;a href=&quot;mailto:contact@bgpkit.com&quot;&gt;email&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get Started&lt;/h2&gt;
&lt;p&gt;Ready to try out the new features? Here are some resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker&quot;&gt;github.com/bgpkit/bgpkit-broker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;: Browse &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker/tree/main/examples&quot;&gt;example code&lt;/a&gt; demonstrating common usage patterns&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Public API&lt;/strong&gt;: Try queries at &lt;a href=&quot;https://api.bgpkit.com&quot;&gt;https://api.bgpkit.com&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have questions or feedback? Open an issue on &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker/issues&quot;&gt;GitHub&lt;/a&gt; or join discussions in our &lt;a href=&quot;https://discord.gg/XDaAtZsz6b&quot;&gt;Discord Channel&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;💖&lt;/h2&gt;
&lt;p&gt;If you find our libraries and services useful, we would highly appreciate if you consider sponsoring us on &lt;a href=&quot;https://github.com/sponsors/bgpkit&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Run BGPKIT on Cloudflare Containers</title><link>https://bgpkit.com/blog/run-bgpkit-on-cloudflare-containers/</link><guid isPermaLink="true">https://bgpkit.com/blog/run-bgpkit-on-cloudflare-containers/</guid><description>For the longest time, I’ve been using Cloudflare exclusively for web/API hosting and relatively lightweight tasks. For the most of my work on BGP, there is real...</description><pubDate>Mon, 30 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the longest time, I’ve been using &lt;a href=&quot;https://developers.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; exclusively for web/API hosting and relatively lightweight tasks. For the most of my work on BGP, there is really not much that I can accomplish with just JavaScript/TypeScript (except maybe working with &lt;a href=&quot;https://ris-live.ripe.net/&quot;&gt;RIS Live&lt;/a&gt; WebSocket). The computationally intensive nature of the most BGP data processing doesn&apos;t naturally fit within the typical Cloudflare developer platform.&lt;/p&gt;
&lt;p&gt;This changes with the recent &lt;a href=&quot;https://blog.cloudflare.com/containers-are-available-in-public-beta-for-simple-global-and-programmable/&quot;&gt;announcement&lt;/a&gt; of &lt;a href=&quot;https://developers.cloudflare.com/containers/&quot;&gt;Cloudflare Containers&lt;/a&gt;. In short, it allows developers to build and run custom containers on the Cloudflare’s platform, enabling the heavy workload to mix with other primitives and unify the deployment.&lt;/p&gt;
&lt;p&gt;In this blog post, I will show you how to build a BGP data search API with BGPKIT and deploy on Cloudflare Containers. The source code is available &lt;a href=&quot;https://github.com/bgpkit/bgpkit-cf-containers&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;BGP Data Search API&lt;/h2&gt;
&lt;p&gt;For this example, I will show you how to build a very straightforward HTTP API to allow accepting search parameters and let BGPKIT to fetch and parse BGP archives and return the parsed messages.&lt;/p&gt;
&lt;p&gt;The API accepts four parameters: &lt;code&gt;collector&lt;/code&gt;, &lt;code&gt;prefix&lt;/code&gt;, &lt;code&gt;ts_start&lt;/code&gt;, and &lt;code&gt;ts_end&lt;/code&gt;, to filter and parse BGP archives efficiently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;collector&lt;/code&gt;: the BGP route collector ID to use (e.g. &lt;code&gt;rrc00&lt;/code&gt; or &lt;code&gt;route-views2&lt;/code&gt;). We want to limit the search to a single collector.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;prefix&lt;/code&gt;: the prefix of the BGP relevant updates. Open-ended search will burn up resources quick, but you can opt-out this requirement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ts_start&lt;/code&gt; and &lt;code&gt;ts_end&lt;/code&gt;: the starting and ending timestamps. The goal is to limit the search to end up parsing a very small amount of MRT files (e.g. give them the same value will do). We should probably leave large-scale data crunching to a environment with more CPU powers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The parameters are defined in a struct to be able to passed in to a &lt;code&gt;axum&lt;/code&gt; “GET” route:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[derive(Deserialize, Serialize)]
struct Params {
    collector: String,
    prefix: String,
    ts_start: String,
    ts_end: String,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the given parameters, we will first find the relevant MRT files by setting the filters of timestamps and collector to &lt;code&gt;BgpkitBroker&lt;/code&gt; instance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;        let files = match bgpkit_broker::BgpkitBroker::new()
            .ts_end(ts_end.clone())
            .ts_start(ts_start.clone())
            .collector_id(collector.clone())
            .query(){
            Ok(items) =&gt; items,
            Err(e) =&gt; {
                return Json(Result {
                    error: Some(e.to_string()),
                    data: vec![],
                    meta: None,
                });
            }
        };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each file, we will parse the whole MRT file and collect BGP updates that is relevant to the target prefix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;        for file in files {
            let mut parser = match bgpkit_parser::BgpkitParser::new(file.url.as_str()){
                Ok(parser) =&gt; parser,
                Err(e) =&gt; {
                    return Json(Result {
                        error: Some(e.to_string()),
                        data: vec![],
                        meta: None,
                    });
                }
            };

            parser = match parser.add_filter(&quot;prefix&quot;, prefix.as_str()){
                Ok(parser) =&gt; parser,
                Err(e) =&gt; {
                    return Json(Result {
                        error: Some(e.to_string()),
                        data: vec![],
                        meta: None,
                    });
                }
            };
            items.extend(parser.into_elem_iter());
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the BGPKIT parser and broker code are implemented in the sync environment, we will need to wrap the code above in a blocking thread in order to use it in async web frameworks like &lt;code&gt;axum&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let result = tokio::task::spawn_blocking(move || {
   // THE BLOCKING CODE PIECES
}).await.unwrap();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please see the full source code here for more details:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-cf-containers/blob/main/container-src/src/main.rs&quot;&gt;https://github.com/bgpkit/bgpkit-cf-containers/blob/main/container-src/src/main.rs&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Cloudflare Containers Deployment&lt;/h2&gt;
&lt;p&gt;Now that we have an Rust-based API code working, we will need to 1. containerize the code, 2. put a Cloudflare Container wrapper around it for deployment.&lt;/p&gt;
&lt;p&gt;The container definition is a typical two-stage build definition, with a builder stage to build the binary application, and a minimum deployment stage to call the executable. The two-stage build is almost necessary as Cloudflare Containers &lt;a href=&quot;https://developers.cloudflare.com/containers/platform-details/#limits&quot;&gt;has limits&lt;/a&gt; on the size of each image and the total storage per account. The smaller the image the better.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;# ---- Build Stage ----
FROM rust:1.86 AS builder

WORKDIR /app

# Install build dependencies
RUN apt-get update &amp;#x26;&amp;#x26; apt-get install -y pkg-config libssl-dev

# Build application
COPY container-src/Cargo.lock container-src/Cargo.toml ./
COPY container-src/src ./src
RUN cargo build --release

# ---- Runtime Stage ----
FROM debian:bookworm-slim

# Install minimal runtime dependencies
RUN apt-get update &amp;#x26;&amp;#x26; apt-get install -y ca-certificates &amp;#x26;&amp;#x26; rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy the statically linked binary from the builder
COPY --from=builder /app/target/release/bgpkit-cf-container /app/bgpkit-cf-container

EXPOSE 3000

CMD [&quot;/app/bgpkit-cf-container&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest of the task is to build a API app for Workers and configure Containers. The following config is pretty much all it needs to configure the Workers script to build and push the container image, and create a &lt;a href=&quot;https://developers.cloudflare.com/durable-objects/&quot;&gt;Durable Object&lt;/a&gt; to coordinate and run Containers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;	&quot;containers&quot;: [
		{
			&quot;class_name&quot;: &quot;BgpkitContainer&quot;,
			&quot;image&quot;: &quot;./Dockerfile&quot;,
			&quot;max_instances&quot;: 5
		}
	],
	&quot;durable_objects&quot;: {
		&quot;bindings&quot;: [
			{
				&quot;class_name&quot;: &quot;BgpkitContainer&quot;,
				&quot;name&quot;: &quot;BGPKIT_CONTAINER&quot;
			}
		]
	},
	&quot;migrations&quot;: [
		{
			&quot;new_sqlite_classes&quot;: [
				&quot;BgpkitContainer&quot;
			],
			&quot;tag&quot;: &quot;v1&quot;
		}
	]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main Workers script is only 22 lines long:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { Container, getContainer } from &apos;@cloudflare/containers&apos;;
import { Hono } from &quot;hono&quot;;

export class BgpkitContainer extends Container {
	defaultPort = 3000;
	sleepAfter = &apos;5m&apos;;
}

// Create Hono app with proper typing for Cloudflare Workers
const app = new Hono&amp;#x3C;{
	Bindings: { BGPKIT_CONTAINER: DurableObjectNamespace&amp;#x3C;BgpkitContainer&gt; };
}&gt;();

app.get(&quot;/search&quot;, async (c) =&gt; {
	if (!c.req.query(&apos;collector&apos;) || !c.req.query(&apos;prefix&apos;) || !c.req.query(&apos;ts_start&apos;) || !c.req.query(&apos;ts_end&apos;)) {
		return c.json({ error: &quot;Missing required query parameters: collector, prefix, ts_start, ts_end&quot; }, 400);
	}
	const container = getContainer(c.env.BGPKIT_CONTAINER);
	return await container.fetch(c.req.raw);
});

export default app;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important pieces are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;class BgpkitContainer extends Container&lt;/code&gt; block defines the port to use and configures how long the container should run after the last query. In this example, containers will be killed after 5 minutes of inactivity. It is crucial to realize that Cloudflare Containers are not a drop-in replacement of some other container deployment platforms like fly.io or Railway, as the workload for Containers are intended to be short-lived (ping me if this changes) and scales horizontally depending on the requests amount.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;getContainer&lt;/code&gt; function here tries to reach a container. If the intended container is overloaded, it may create a new container on demand. You may choose to use the &lt;code&gt;getRandom&lt;/code&gt; function to round-robin containers. See &lt;a href=&quot;https://developers.cloudflare.com/containers/scaling-and-routing/&quot;&gt;the docs&lt;/a&gt; for more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;container.fetch(c.req.raw)&lt;/code&gt; forwards the query to the container, including the query parameters, which will then be handled by the running container.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example Queries&lt;/h2&gt;
&lt;p&gt;The following example will reach the Workers script (handled by Hono), and then reach the container to run the actual BGP data crunching task. (This URL won’t actually work as we don’t have budget to provide such service openly. Feel free to deploy it on your own account to try it out.)&lt;br&gt;
&lt;a href=&quot;https://bgpkit-cf-containers.bgpkit.workers.dev/search?collector=rrc00&amp;#x26;prefix=103.228.200.0/24&amp;#x26;ts_start=1751231488&amp;#x26;ts_end=1751231488&quot;&gt;https://EXAMPLE.bgpkit.workers.dev/search?collector=rrc00&amp;#x26;prefix=1.1.1.0/24&amp;#x26;ts_start=1751231488&amp;#x26;ts_end=1751231488&lt;/a&gt;&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/api-query-1.Cgh5JWEo.png&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/api-query-2.BvDty7sk.png&quot; alt=&quot;&quot;&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>BGPKIT Broker 0.7 Release</title><link>https://bgpkit.com/blog/bgpkit-broker-07-release/</link><guid isPermaLink="true">https://bgpkit.com/blog/bgpkit-broker-07-release/</guid><description>BGPKIT Broker is a fundamental component to our design of a all-purpose BGP data processing pipeline. In short, it is a BGP data file meta information broker...</description><pubDate>Sat, 22 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker&quot;&gt;BGPKIT Broker&lt;/a&gt; is a fundamental component to our design of a all-purpose BGP data processing pipeline. In short, it is a BGP data file meta information &quot;broker&quot; that tells the data consumers what MRT files from RouteViews and RIPE RIS are available for any given time range in question. It commonly serves as a data input entry point for data pipelines.&lt;/p&gt;
&lt;p&gt;For instance, here is a simple diagram for a system that creates a semi-real-time BGP data stream with BGPKIT Broker and Parser (a very common use case for these two libraries).&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/broker-parser-workflow.Dqqya9Is.png&quot; alt=&quot;Sample workflow diagram where BGPKIT Broker indexes meta information from MRT archives and BGPKIT parser parses these files to BGP messages.&quot;&gt;
&lt;p&gt;BGPKIT Broker periodically crawls the websites of RIPE RIS and RouteViews MRT data pages of their collectors and index meta information into a database. Downstream consumers can ask and retrieve new files and process the files into BGP messages.&lt;/p&gt;
&lt;h1&gt;Previously on BGPKIT Broker&lt;/h1&gt;
&lt;p&gt;In the BGPKIT Broker version 0.1 to 0.6, a working broker instance consists of three individual components: a crawler, a Postgres database, and an API.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/old-architecture.BBsyc0qO.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Each of the three component runs independently, and requires independent configuration, cronjobs, deployment, and all these goodies. For example, to run BGPKIT Broker v0.6, a user will need to configure and run&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a PostgreSQL database with proper credentials and schema set up;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a cronjob instance that periodically crawls the data sources, with optional locks to prevent overlapping executions in case some crawl became slow;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a API application likely sitting behind a configured reverse proxy like Caddy to serve the data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s fun and exciting to set up all these for the first time, but quickly became tiring and too complex for repeated set up or bootstrapping for new users.&lt;/p&gt;
&lt;h1&gt;V0.7: one CLI app that does everything&lt;/h1&gt;
&lt;p&gt;We completely revamped the architecture for BGPKIT Broker in V0.7 to merge every functionality needed for a running Broker instance into one single command-line application: &lt;code&gt;bgpkit-broker&lt;/code&gt;. V0.7 provides a single application to configure, run, debug, query everything on BGPKIT Broker.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/v07-single-cli.Bb1TfS4H.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;To achieve this redesign, we made some major changes to our architecture.&lt;/p&gt;
&lt;h2&gt;SQLite instead of PostgreSQL&lt;/h2&gt;
&lt;p&gt;There are two major topics to concern when choosing a backend database for BGPKIT Broker: performance and portability.&lt;/p&gt;
&lt;h3&gt;SQLite is more than fast enough&lt;/h3&gt;
&lt;p&gt;BGPKIT Broker indexes metadata for &lt;strong&gt;all collectors&lt;/strong&gt; from RouteViews and RIPE RIS, which includes time, URL, type, size of every RIB dump and updates MRT files from these two public archives. Dating all the way back to 1999, we have indexed roughly 48 million MRT files&apos; metadata.&lt;/p&gt;
&lt;p&gt;With a single index on the timestamp of files, we are able to search data files in less than 0.5s for any queries, which is more than fast enough for our use cases. We admit that we have spent our time for &quot;early optimization&quot; and in the end, the simple schema out-weighs the small performance gains.&lt;/p&gt;
&lt;h3&gt;Backup and bootstrap with just one file&lt;/h3&gt;
&lt;p&gt;Now in terms of portability, we can appreciate enough the beauty of single-file database like SQLite. In our current production setup, we periodically backup the database, and it literally involves just copying a single file to another directory (well, we also upload it to Cloudflare R2 for safekeeping).&lt;/p&gt;
&lt;p&gt;Portability also means users can move their instance anywhere they want with ease. This is definitely the case for V0.7 where new users can bootstrap by simply download a SQLite file (our CLI provides all that functionality), and move to new locations by scp it to anywhere they desire.&lt;/p&gt;
&lt;p&gt;Here is a video demonstrating bootstrapping a local BGPKIT Broker SQLite database with the new &lt;code&gt;bgpkit-broker bootstrap&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;%[&lt;a href=&quot;https://youtu.be/SsCyfQ5q0G0&quot;&gt;https://youtu.be/SsCyfQ5q0G0&lt;/a&gt;]&lt;/p&gt;
&lt;h2&gt;New file notification via NATS&lt;/h2&gt;
&lt;p&gt;Before V0.7, pipelines that needs to continuously processing new MRT files will need to &quot;pull&quot; data from BGPKIT Broker instance periodically and keep track of the latest files processed. We consider this a hassle that developers should not be dealing with and thus introduced a new &lt;a href=&quot;https://nats.io/&quot;&gt;&lt;code&gt;NATS&lt;/code&gt;&lt;/a&gt;-based message channel allowing data consumers to subscribe to the public/private NATS channel where a Broker instance may publish new file notification to.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/nats-notification.BRitxG0Y.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;We dedicated &lt;code&gt;nats.broker.bgpkit.com&lt;/code&gt; as the public endpoint for any NATS consumers to connect to. Whenever a new file becomes available in Broker, it will publish a new file notification with all metadata as in the database entry to the public channel. Consumers (e.g. data pipelines) can use the &lt;code&gt;NatsNotifier::new(None).start_subscription()&lt;/code&gt; to start waiting for new files. The following snippet below shows how a simple pipeline can use this feature in a loop.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut notifier = match NatsNotifier::new(url).await {
    Ok(n) =&gt; n,
    Err(e) =&gt; {
        error!(&quot;{}&quot;, e);
        return;
    }
};
if let Err(e) = notifier.start_subscription(subject).await {
    error!(&quot;{}&quot;, e);
    return;
}
while let Some(item) = notifier.next().await {
    if pretty {
        println!(&quot;{}&quot;, serde_json::to_string_pretty(&amp;#x26;item).unwrap());
    } else {
        println!(&quot;{}&quot;, item);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also implemented a simple new file watcher in the app as &lt;code&gt;bgpkit-broker live&lt;/code&gt; subcommand. It will start a subscription to the public BGPKIT NATS endpoint and print out new file data as they come to the channel.&lt;/p&gt;
&lt;h2&gt;One command to serve and update&lt;/h2&gt;
&lt;p&gt;As mentioned previously, the new &lt;code&gt;bgpkit-broker&lt;/code&gt; application includes everything one needs to start a instance. Once one bootstrapped the database to a local sqlite file (via &lt;code&gt;bgpkit-broker bootstrap &amp;#x3C;FILENAME&gt;&lt;/code&gt; command), all they need to start a auto-updating API is to run &lt;code&gt;bgpkit-broker serve &amp;#x3C;FILENAME&gt;&lt;/code&gt; .&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bgpkit-broker serve --help
Serve the Broker content via RESTful API

Usage: bgpkit-broker serve [OPTIONS] &amp;#x3C;DB_PATH&gt;

Arguments:
  &amp;#x3C;DB_PATH&gt;  broker db file location

Options:
  -i, --update-interval &amp;#x3C;UPDATE_INTERVAL&gt;  update interval in seconds [default: 300]
      --no-log                             disable logging
  -b, --bootstrap                          bootstrap the database if it does not exist
      --env &amp;#x3C;ENV&gt;                          
  -s, --silent                             disable bootstrap progress bar
  -h, --host &amp;#x3C;HOST&gt;                        host address [default: 0.0.0.0]
  -p, --port &amp;#x3C;PORT&gt;                        port number [default: 40064]
  -r, --root &amp;#x3C;ROOT&gt;                        root path, useful for configuring docs UI [default: /]
      --no-update                          disable updater service
      --no-api                             disable API service
  -h, --help                               Print help
  -V, --version                            Print version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;serve&lt;/code&gt; subcommand will also start a thread that periodically crawl and update the SQLite database to make sure the API always serve the up-to-date data.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/nats-error.BR87Fj68.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Noticed that error message? It&apos;s by design as it tries to connect to notification channel for new files as a default behavior for a service, but not NATS URL is configured. We use the &lt;code&gt;BGPKIT_BROKER_NATS_URL&lt;/code&gt; environment variable to configure the NATS channel to use.&lt;/p&gt;
&lt;p&gt;We also allow users to optionally configure a heartbeat URL to monitor the data updating status. After every success data crawling run, Broker will try to execute a HTTP GET to a URL if &lt;code&gt;BGPKIT_BROKER_HEARTBEAT_URL&lt;/code&gt; is set in the environment. This is useful to monitor the running status of the Broker instance without the need of setting up a cronjob.&lt;/p&gt;
&lt;p&gt;We use &lt;a href=&quot;https://betterstack.com/&quot;&gt;Better Stack&apos;s Uptime&lt;/a&gt; monitoring service for page and heartbeat monitoring, and the public Broker instance is running V0.7 with the heartbeat URL set to this service. All status information can be found at &lt;a href=&quot;https://status.bgpkit.com/&quot;&gt;https://status.bgpkit.com/&lt;/a&gt;&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/uptime-status.COEXwns7.png&quot; alt=&quot;&quot;&gt;
&lt;h1&gt;Production-ready, on-prem deployment&lt;/h1&gt;
&lt;p&gt;Although BGPKIT Broker has not yet reached V1.0, we consider it to be feature-complete and production-ready. Ever since V0.2, we have made our better efforts on not introducing any breaking changes and the service has been serving the community with a stable uptime ever since. We believe all libraries running in production should at least be 1.0, and thus &lt;strong&gt;we will release V1.0 soon this summer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We also made significant efforts in V0.7 release to make sure BGPKIT Broker is as portable as possible. New users can spin up a fully functioning Broker instance with just two commands: &lt;code&gt;bgpkit-broker bootstrap&lt;/code&gt; and &lt;code&gt;bgpkit-broker serve&lt;/code&gt;, all within 5 minutes. With V0.7 released, we &lt;strong&gt;encourage all data pipeline designers to deploy a Broker instance on-premise&lt;/strong&gt;, ensuring data pipelines are self-containing and reduce external dependencies as much as possible. We also will continue maintain our public instance to our best efforts (we are currently at &lt;strong&gt;99.996% uptime&lt;/strong&gt;). Thanks to &lt;a href=&quot;https://github.com/sponsors/bgpkit&quot;&gt;our sponsors&lt;/a&gt;, we are able to keep the services up as we do, and we plan to continue serving the community the same way in the foreseeable future.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/broker-website.D64RUVmg.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;For the full V0.7 release notes, please check out our &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker/releases/tag/v0.7.0&quot;&gt;GitHub release page&lt;/a&gt;. If you have any comments, please drop us a message at &lt;a href=&quot;https://twitter.com/bgpkit&quot;&gt;Twitter&lt;/a&gt;, &lt;a href=&quot;https://infosec.exchange/@bgpkit&quot;&gt;Mastodon&lt;/a&gt;, or &lt;a href=&quot;mailto:contact@bgpkit.com&quot;&gt;email&lt;/a&gt;.&lt;/p&gt;
&lt;div data-node-type=&quot;callout&quot;&gt;
&lt;div data-node-type=&quot;callout-emoji&quot;&gt;💖&lt;/div&gt;
&lt;div data-node-type=&quot;callout-text&quot;&gt;If you find our libraries and services useful, we would highly appreciate if you consider &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot; href=&quot;https://github.com/sponsors/bgpkit&quot; style=&quot;pointer-events: none&quot;&gt;sponsor us on GitHub&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Command-line Routing Stats with monocle and Cloudflare Radar API</title><link>https://bgpkit.com/blog/monocle-cloudflare-radar/</link><guid isPermaLink="true">https://bgpkit.com/blog/monocle-cloudflare-radar/</guid><description>BGPKIT monocle is a command-line utility program that helps users quickly pull Internet routing-related information from publicly available sources.</description><pubDate>Sun, 21 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BGPKIT &lt;code&gt;monocle&lt;/code&gt; is a command-line utility program that helps users quickly pull Internet routing-related information from publicly available sources.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;https://github.com/bgpkit/monocle&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In BGPKIT &lt;code&gt;monocle&lt;/code&gt; version V0.5, we add support for querying &lt;a href=&quot;https://radar.cloudflare.com/&quot;&gt;Cloudflare Radar&lt;/a&gt;&apos;s new BGP &lt;a href=&quot;https://developers.cloudflare.com/api/operations/radar-get-bgp-routes-stats&quot;&gt;routing statistics&lt;/a&gt; and &lt;a href=&quot;https://developers.cloudflare.com/api/operations/radar-get-bgp-pfx2as&quot;&gt;prefix-to-origin mapping&lt;/a&gt; APIs, the same APIs that power the &lt;a href=&quot;https://radar.cloudflare.com/routing&quot;&gt;Cloudflare Radar routing section&lt;/a&gt;. &lt;code&gt;monocle&lt;/code&gt; users can now quickly glance overview of routing stats for any given ASN, country, or the whole Internet. Users can also quickly look up prefix origins and examine their RPKI validation status as well as prefix visibility on the global routing tables.&lt;/p&gt;
&lt;h2&gt;Using &lt;code&gt;monocle radar&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We added a new &lt;code&gt;monocle radar&lt;/code&gt; command group in V0.5, which contains the following to subcommands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;monocle radar stats [QUERY]&lt;/code&gt;: get routing stats (like prefix count, rpki invalid count) for a given country or ASN.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;monocle radar pfx2as [QUERY] [--rpki-status valid|invalid|unknown]&lt;/code&gt;: get prefix to origin mapping for a given prefix or ASN&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;mingwei@terrier ~ % monocle radar
Cloudflare Radar API lookup (set CF_API_TOKEN to enable)

Usage: monocle radar &amp;#x3C;COMMAND&gt;

Commands:
  stats   get routing stats
  pfx2as  look up prefix to origin mapping on the most recent global routing table snapshot
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Cloudflare API token needed&lt;/h3&gt;
&lt;p&gt;Since the &lt;code&gt;monocle radar&lt;/code&gt; command relies on querying data using Cloudflare Radar public API, we also need to specify a user API token as the environment variable &lt;code&gt;CF_API_TOKEN&lt;/code&gt;. Obtaining an API token is free and only needs a Cloudflare account. Interested users can follow their &lt;a href=&quot;https://developers.cloudflare.com/radar/get-started/first-request/&quot;&gt;official tutorial&lt;/a&gt; to obtain a token. The environment variable can be set in a &lt;code&gt;.env&lt;/code&gt; file in the current directory, or set in ~&lt;code&gt;/.bashrc&lt;/code&gt; or &lt;code&gt;~/.profile&lt;/code&gt; etc.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;monocle radar stats&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Users can query the routing statistics for a given country or ASN. For example, &lt;code&gt;monocle radar stats us&lt;/code&gt; returns the routing stats for the United States, while &lt;code&gt;monocle radar stats 174&lt;/code&gt; returns the stats for Cogent (&lt;code&gt;AS174&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The displayed table is further divided into three rows, one for overall counting, and one for IPv4 and IPv6-specific counting. For each row, we show the following fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;origins&lt;/code&gt;: the number of origins ASes registered in the given country&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;prefixes&lt;/code&gt;: the number of prefixes originated by the given ASN or ASes registered in the given country&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;rpki_valid/invalid/unknown&lt;/code&gt;: the number of RPKI valid/invalid/unknown prefix routes (prefix-origin mapping) on the global routing table and their percentage of the overall routes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/radar-stats-output.DtnTcW2F.png&quot; alt=&quot;&quot;&gt;
&lt;h3&gt;&lt;code&gt;monocle radar pfx2as&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Users can query the prefix-to-origin API to get the mapping of origin ASes and their originated prefixes on the global routing table.&lt;/p&gt;
&lt;p&gt;In the following example, &lt;code&gt;monocle radar pfx2as 174 --rpki-status invalid&lt;/code&gt;, we ask for all the prefixes originated by &lt;code&gt;AS174&lt;/code&gt; with the RPKI validation status to be invalid. This command returns us the list of RPKI invalid prefixes originated by &lt;code&gt;AS174&lt;/code&gt; at the time of generating the dataset.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/radar-pfx2as-output.C8DHRa_F.png&quot; alt=&quot;&quot;&gt;
&lt;h3&gt;Questions it can answer now (more in the future)&lt;/h3&gt;
&lt;p&gt;Here is a selected list of questions that &lt;code&gt;monocle radar&lt;/code&gt; command can answer you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;How many ASes are there on the Internet that announce at least one prefix? (81,770)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How many of these ASes announce only IPv6 prefixes? (6,853)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How many prefixes are there on the global routing table? (1,205,218)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How many prefixes do &lt;code&gt;AS400644&lt;/code&gt; announce? (1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which AS(es) originates &lt;code&gt;1.1.1.0/24&lt;/code&gt;? (AS13335)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How many prefixes originated by &lt;code&gt;AS174&lt;/code&gt; are NOT covered by some RPKI ROA? (a lot, 94%+)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How about the RPKI valid ratio for the Philippines? (77%, nice!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Powered by Cloudflare Radar free API&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Cloudflare Radar is a hub that showcases global Internet traffic, attack, and technology trends and insights.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What &lt;a href=&quot;https://radar.cloudflare.com/&quot;&gt;Cloudflare Radar&lt;/a&gt; shines is its data openness. Everything you see on the Cloudflare Radar website is powered by their free &lt;a href=&quot;https://developers.cloudflare.com/api/operations/radar-get-bgp-pfx2as-moas&quot;&gt;publicly available APIs&lt;/a&gt;. It&apos;s a treasure trove there, and all users need is a &lt;a href=&quot;https://developers.cloudflare.com/radar/get-started/first-request/&quot;&gt;free API token&lt;/a&gt; to access everything.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/cloudflare-radar-site.DxotvYqB.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;At BGPKIT, we think we can further improve the usability of the API by exposing them as a proper Rust SDK: &lt;a href=&quot;https://github.com/bgpkit/radar-rs&quot;&gt;&lt;code&gt;radar-rs&lt;/code&gt;&lt;/a&gt;. This is our (unofficial) effort on bringing the Cloudflare Radar&apos;s rich data to Rust developers. For example, &lt;code&gt;monocle radar&lt;/code&gt; is powered by this SDK.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>ASPA Path Verification Explained</title><link>https://bgpkit.com/blog/aspa-path-verification-explained/</link><guid isPermaLink="true">https://bgpkit.com/blog/aspa-path-verification-explained/</guid><description>An explanation of Autonomous System Provider Authorization (ASPA) path verification and how it detects route leaks and improbable AS paths.</description><pubDate>Sun, 25 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://rfc.hashnode.dev/aspa-path-verification-explained&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Autonomous System Provider Authorization (ASPA) is a recently proposed
solution designed to detect and mitigate route leaks and improbable AS
paths. ASPA utilizes the existing RPKI infrastructure to allow storing
and verifying cryptographically protected AS-level relationship
information. This post focuses on the AS path verification process used
by ASPA.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post is based on &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/&quot;&gt;this IETF draft&lt;/a&gt; from June 2023. The draft text, including address-family handling, may differ from the final RFC.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Route leak&lt;/h2&gt;
&lt;p&gt;In overview, the definition of route leak considered in ASPA is based on
&lt;a href=&quot;https://datatracker.ietf.org/doc/rfc7908/&quot; target=&quot;_blank&quot;&gt;RFC
7908: Problem Definition and Classification of BGP Route Leaks&lt;/a&gt;. The
route leaks defined in this RFC is mostly based on the
&lt;a href=&quot;https://ieeexplore.ieee.org/document/974527&quot; target=&quot;_blank&quot;&gt;&quot;valley-free&quot; principle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A previous Cloudflare Radar post on &lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar/#route-leak-definition-and-types&quot; target=&quot;_blank&quot;&gt;route-leak detection&lt;/a&gt; summarized four common route
leak types as follows:&lt;/p&gt;
&lt;div class=&quot;hn-table&quot;&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Routes from&lt;/th&gt;&lt;th&gt;To provider&lt;/th&gt;&lt;th&gt;To peer&lt;/th&gt;&lt;th&gt;To customer&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;From provider&lt;/td&gt;&lt;td&gt;Type 1&lt;/td&gt;&lt;td&gt;Type 3&lt;/td&gt;&lt;td&gt;Normal&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;From peer&lt;/td&gt;&lt;td&gt;Type 4&lt;/td&gt;&lt;td&gt;Type 2&lt;/td&gt;&lt;td&gt;Normal&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;From customer&lt;/td&gt;&lt;td&gt;Normal&lt;/td&gt;&lt;td&gt;Normal&lt;/td&gt;&lt;td&gt;Normal&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;In essence, route leaks defined in RFC 7908 can be summarized into one
principle: &lt;strong&gt;routes obtained from a non-customer (peer, customer,
route-server) AS can only be propagated to customers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In ASPA, the authors inherited the definition of BGP roles (e.g.
customer, provider) from &lt;a href=&quot;https://bgpkit.com/blog/rfc9234-observed-in-the-wild/#heading-rfc9234&quot; target=&quot;_blank&quot;&gt;RFC 9234&lt;/a&gt;. See the &lt;a href=&quot;https://bgpkit.com/blog/rfc9234-observed-in-the-wild/#heading-rfc9234&quot; target=&quot;_blank&quot;&gt;RFC9234 post&lt;/a&gt; for more detail on RFC9234 and the OTC
BGP attribute.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Local AS Role&lt;/th&gt;&lt;th&gt;Remote AS Role&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Provider&lt;/td&gt;&lt;td&gt;Customer&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Customer&lt;/td&gt;&lt;td&gt;Provider&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RS&lt;/td&gt;&lt;td&gt;RS-Client&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RS-Client&lt;/td&gt;&lt;td&gt;RS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Peer&lt;/td&gt;&lt;td&gt;Peer&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Table: allowed pairs of BGP role capabilities.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;ASPA TL;DR&lt;/h2&gt;
&lt;p&gt;At a high level, the ASPA mechanism has the following properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ASPA stores &lt;code&gt;customer-to-provider&lt;/code&gt; information in RPKI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One customer AS (CAS) associated with a set of provider ASes (SPAS)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ASPA is useful for verifying BGP &lt;code&gt;AS_PATH&lt;/code&gt; attribute content&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AS_PATH&lt;/code&gt; that violates the &quot;valley-free&quot; routing principle can be
detected with ASPA objects information&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ASPA is incrementally deployable, benefiting early adopters&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ASPA Verification Explained&lt;/h2&gt;
&lt;p&gt;Before describing how ASPA detects route leaks, it is useful to review
the shape of a typical valley-free route.&lt;/p&gt;
&lt;p&gt;A typical long BGP route that travels from one edge network to a
different edge network would look like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar/#route-leak-definition-and-types&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://bgpkit.com/blog-assets/rfc-hashnode/aspa-path-verification-explained/image-2.png&quot; alt=&quot;Typical valley-free AS path with customer-provider, peer-peer, and provider-customer phases&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The route travels three different phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;upward: a consecutive &lt;code&gt;customer-to-provider&lt;/code&gt; segments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;peering: a single &lt;code&gt;peer-to-peer&lt;/code&gt; segment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;downward: a consecutive &lt;code&gt;provider-to-customer&lt;/code&gt; segments&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that in many cases, the routes may only have one or two phases, but
they must be in this order. For example, one legit route could have an
&lt;code&gt;upward&lt;/code&gt; then &lt;code&gt;downward&lt;/code&gt; phase, without a &lt;code&gt;peering&lt;/code&gt; phase. A route leak
route could be any route with reverse order of the phases, e.g.
&lt;code&gt;downward&lt;/code&gt; then &lt;code&gt;upward&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In ASPA, the phases are defined into two categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;upstream&lt;/code&gt;: path received from a customer or a peer or a route server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;downstream&lt;/code&gt;: path received from a provider or sibling&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ASPA verification uses different procedures for &lt;code&gt;upstream&lt;/code&gt; and
&lt;code&gt;downstream&lt;/code&gt; paths. Each category is described below.&lt;/p&gt;
&lt;h3&gt;Upstream paths&lt;/h3&gt;
&lt;p&gt;ASPA verification for upstream paths (received from a customer or peer
or route server) is relatively simple. The key principle is that &lt;strong&gt;a
normal upstream path should only contain&lt;/strong&gt; &lt;code&gt;customer-to-provider&lt;/code&gt;
&lt;strong&gt;relationships&lt;/strong&gt; for the consecutive AS path hops.&lt;/p&gt;
&lt;p&gt;A path is&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Valid&lt;/code&gt;: if all hops in the AS paths are verifiable
&lt;code&gt;customer-to-provider&lt;/code&gt;, i.e. every hop on the path registers their
providers on ASPA and the providers match the next hop on the path;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Unknown&lt;/code&gt;: one of the hops is missing ASPA information;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Invalid&lt;/code&gt;: one of AS has a set of provider AS (SPAS) but the next hop
is not in it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Downstream paths&lt;/h3&gt;
&lt;p&gt;The downstream paths are considerably more complex as it may have legit
paths segments that travel all three phases (up, peer, down) before
reaching the receiving AS. Interested readers should refer to the
original IETF draft document&apos;s &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-aspa-verification-14#name-algorithm-for-downstream-pa&quot; target=&quot;_blank&quot;&gt;section on downstream path verification&lt;/a&gt; for a more
definitive definition. The summary below simplifies the wording to give
a high-level view of the verification process.&lt;/p&gt;
&lt;p&gt;Based on the original documentation, a &lt;code&gt;downstream&lt;/code&gt; path is &lt;code&gt;Invalid&lt;/code&gt; if
there exist hops indexed as &lt;code&gt;u&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; such that&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;u&amp;#x3C;=v&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hop(AS(u-1), AS(u))=&quot;Not Provider+&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hop(AS(v+1),AS(v))=&quot;Not Provider+&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In practical terms, this condition can be read as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;two hops at the location &lt;code&gt;u&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; and &lt;code&gt;u&lt;/code&gt; is before &lt;code&gt;v&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hop &lt;code&gt;u&lt;/code&gt; is verified to be NOT a customer-to-provider hop, i.e.
&lt;code&gt;peer-to-peer&lt;/code&gt; or &lt;code&gt;provider-to-customer&lt;/code&gt;, i.e. a &lt;code&gt;peering&lt;/code&gt; or
&lt;code&gt;downward&lt;/code&gt; phase&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hop v is verified to be NOT a provider-to-customer hop, i.e.
&lt;code&gt;peer-to-peer&lt;/code&gt; or &lt;code&gt;customer-to-provider&lt;/code&gt;, i.e. a &lt;code&gt;peering&lt;/code&gt; or
&lt;code&gt;upward&lt;/code&gt; phase&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In summary, the path would be ASPA &lt;code&gt;Invalid&lt;/code&gt;, if it contains a
&lt;code&gt;downward&lt;/code&gt; or &lt;code&gt;peering&lt;/code&gt; then later a &lt;code&gt;upward&lt;/code&gt; or &lt;code&gt;peering&lt;/code&gt;. The AS
relationship would look like the following figure, where &lt;code&gt;u&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt;
could be the same ASN. As long as this pattern appears somewhere in the
path, it violates the &quot;valley-free&quot; routing and thus is considered as
route leak.&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;graph LR
  UProvider[&quot;u-1&quot;] --&gt;|&quot;provider-to-customer&quot;| U[&quot;u&quot;]
  UPeer[&quot;u-1&quot;] --&gt;|&quot;peer-to-peer&quot;| U
  U -.-&gt; V[&quot;v&quot;]
  V --&gt;|&quot;customer-to-provider&quot;| VProvider[&quot;v+1&quot;]
  V --&gt;|&quot;peer-to-peer&quot;| VPeer[&quot;v+1&quot;]
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Unknown&lt;/code&gt; and &lt;code&gt;Valid&lt;/code&gt; definitions are then straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Unknown&lt;/code&gt;: the path is NOT &lt;code&gt;Invalid&lt;/code&gt; and there are hops that miss ASPA
information&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Valid&lt;/code&gt;: the path is NOT &lt;code&gt;Invalid&lt;/code&gt; and NOT &lt;code&gt;Unknown&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-sidrops-aspa-verification-14#section-7&quot;&gt;ASPA document&lt;/a&gt; allows different verification algorithms, as long as they produce results equivalent to the specified process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;ASPA vs RFC9234&lt;/h2&gt;
&lt;p&gt;ASPA and RFC 9234 both aim to prevent or mitigate route leaks, but they
use different mechanisms. The table below summarizes the main
differences.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;ASPA&lt;/th&gt;&lt;th&gt;RFC9234&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;By whom&lt;/td&gt;&lt;td&gt;Customer AS (owner) only&lt;/td&gt;&lt;td&gt;BGP sessions, providers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Content security&lt;/td&gt;&lt;td&gt;Cryptographically signed&lt;/td&gt;&lt;td&gt;Secured by BGP session; OTC attributes can be forged&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Where to detect/filter&lt;/td&gt;&lt;td&gt;Any AS&lt;/td&gt;&lt;td&gt;Neighbors or ASes that receive OTC attribute&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Prevent forged path?&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Deployment timing&lt;/td&gt;&lt;td&gt;Initial objects had already appeared; general deployment was expected in 2025 or later (&lt;a href=&quot;https://www.manrs.org/2023/05/estimating-the-timeline-for-aspa-deployment/&quot;&gt;Job Snijders / MANRS&lt;/a&gt;)&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://bgpkit.com/blog/rfc9234-observed-in-the-wild/#heading-only-to-customer-attribute-in-the-wild&quot;&gt;Initial deployment had appeared&lt;/a&gt;; general deployment remained unclear&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;How to view ASPA objects on RPKI today?&lt;/h2&gt;
&lt;p&gt;Although still in the draft phase, ASPA support has already been added
to popular routing software like
&lt;a href=&quot;https://github.com/NLnetLabs/routinator/pull/847&quot; target=&quot;_blank&quot;&gt;routinator&lt;/a&gt; and
&lt;a href=&quot;https://twitter.com/openbsd/status/1636785866596286465&quot; target=&quot;_blank&quot;&gt;OpenBGPd&lt;/a&gt;. BGPKIT monocle also supports reading ASPA
objects from RPKI repositories through the
&lt;a href=&quot;https://github.com/bgpkit/monocle#monocle-rpki-read-aspa&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;monocle rpki read-aspa&lt;/code&gt;&lt;/a&gt; command. The following
steps show how to inspect currently published ASPA objects.&lt;/p&gt;
&lt;h3&gt;Install BGPKIT monocle&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/monocle&quot; target=&quot;_blank&quot;&gt;BGPKIT
monocle&lt;/a&gt; is a Rust-based BGP data processing command-line tool.
Install the Rust toolchain using the instructions at
&lt;a href=&quot;https://rustup.rs/&quot; target=&quot;_blank&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://rustup.rs/&quot;&gt;https://rustup.rs/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then install &lt;code&gt;monocle&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install monocle
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Download the recent RPKI archive&lt;/h3&gt;
&lt;p&gt;The RPKI repository archive used here comes from Job Snijders&apos;
&lt;a href=&quot;http://www.rpkiviews.org/&quot; target=&quot;_blank&quot;&gt;RPKIViews&lt;/a&gt;
project. The example below downloads one archive from
&lt;a href=&quot;http://josephine.sobornost.net/josephine.sobornost.net/&quot; target=&quot;_blank&quot;&gt;josephine.sobornost.net&lt;/a&gt; (about 260 MB).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /tmp
wget http://josephine.sobornost.net/josephine.sobornost.net/rpkidata/2023/06/25/rpki-20230625T173535Z.tgz
tar xzf rpki-20230625T173535Z.tgz
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Find and view ASPA objects&lt;/h3&gt;
&lt;p&gt;The ASPA objects are stored as files with the suffix &lt;code&gt;.asa&lt;/code&gt; in the RPKI
repository. You can use &lt;code&gt;find&lt;/code&gt; command to search across the untarred
repo file from the previous step:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  rpki-20230625T173535Z find . -name &quot;*.asa&quot;
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148808.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148809.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148810.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148804.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148805.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148807.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148806.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148802.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148803.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148801.asa
./data/rpki.sub.apnic.net/repository/A914BC7A0000/0/AS148800.asa
./data/rpki.cc/repo/MythicalKitten/9/AS60310.asa
./data/rpki.cc/repo/MythicalKitten/0/AS203635.asa
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;monocle rpki read-aspa&lt;/code&gt; to inspect an ASPA object. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  rpki-20230625T173535Z monocle rpki read-aspa ./data/rpki.cc/repo/MythicalKitten/9/AS60310.asa

| asn   | afi_limit | allowed_upstream |
|-------|-----------|------------------|
| 60310 | none      | 924              |
|       |           | 6939             |
|       |           | 48581            |
|       |           | 50224            |
|       |           | 52210            |
|       |           | 204857           |
➜  rpki-20230625T173535Z monocle rpki read-aspa ./data/rpki.co/repo/Mlgt/0/AS204508.asa

| asn    | afi_limit | allowed_upstream |
|--------|-----------|------------------|
| 204508 | none      | 6939             |
|        |           | 7721             |
|        |           | 29632            |
|        |           | 34872            |
|        |           | 34927            |
|        |           | 38230            |
|        |           | 41051            |
|        |           | 50058            |
|        |           | 60326            |
|        |           | 200105           |
|        |           | 208753           |
|        |           | 210633           |
|        |           | 211411           |
|        |           | 212271           |
|        |           | 212895           |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit&quot; target=&quot;_blank&quot;&gt;BGPKIT&lt;/a&gt; is
experimenting with library support for ASPA to support measurement and
research use cases. Feedback and feature requests are welcome at
&lt;a href=&quot;mailto:data@bgpkit.com&quot;&gt;data@bgpkit.com&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>RFCs for IP Address Usage in Network Documentation and Examples</title><link>https://bgpkit.com/blog/rfcs-for-ip-address-usage-in-documentation/</link><guid isPermaLink="true">https://bgpkit.com/blog/rfcs-for-ip-address-usage-in-documentation/</guid><description>A short guide to the RFC-defined IPv4 and IPv6 address ranges reserved for documentation and examples.</description><pubDate>Wed, 22 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://rfc.hashnode.dev/rfcs-for-ip-address-usage-in-documentation&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Network documentation and topology examples should use address ranges
reserved for documentation, rather than globally routed addresses such as
&lt;code&gt;1.1.1.1&lt;/code&gt; or &lt;code&gt;1.2.3.4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two RFCs define standardized IP address ranges reserved specifically for
documentation and examples: &lt;a href=&quot;https://tools.ietf.org/html/rfc3849&quot; target=&quot;_blank&quot;&gt;RFC3849&lt;/a&gt; and
&lt;a href=&quot;https://tools.ietf.org/html/rfc5737&quot; target=&quot;_blank&quot;&gt;RFC5737&lt;/a&gt;. RFC3849 outlines the use of the IPv6
address prefix &lt;code&gt;2001:DB8::/32&lt;/code&gt;, while RFC5737 outlines the use of
several IPv4 address blocks, including &lt;code&gt;192.0.2.0/24&lt;/code&gt;,
&lt;code&gt;198.51.100.0/24&lt;/code&gt;, and &lt;code&gt;203.0.113.0/24&lt;/code&gt;. These addresses are not
intended for use in a production network but instead serve as
placeholders to ensure accurate and clear documentation.&lt;/p&gt;
&lt;h3&gt;RFC3849: IPv6 Address Prefix Reserved for Documentation&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc3849&quot; target=&quot;_blank&quot;&gt;RFC3849&lt;/a&gt; specifies the IPv6 address prefix
&lt;code&gt;2001:DB8::/32&lt;/code&gt; for use in documentation and example code, not in a
production network. For example, a network administrator might use the
IPv6 address &lt;code&gt;2001:DB8::1&lt;/code&gt; in their documentation or example code to
represent a specific device or network location. This address would not
be used for actual network traffic, but rather as a reference point for
documentation purposes.&lt;/p&gt;
&lt;h3&gt;RFC5737: IPv4 Address Blocks Reserved for Documentation&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc5737&quot; target=&quot;_blank&quot;&gt;RFC5737&lt;/a&gt; outlines the use of several IPv4 address
blocks for use in documentation and example code. These address blocks
are similar to the IPv4 addresses outlined in
&lt;a href=&quot;https://tools.ietf.org/html/rfc5735&quot; target=&quot;_blank&quot;&gt;RFC5735&lt;/a&gt; (which contains more special blocks such as
&lt;code&gt;0.0.0.0/8&lt;/code&gt;), but are specifically for use in documentation and
examples.&lt;/p&gt;
&lt;p&gt;The address blocks outlined in RFC5737 include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;192.0.2.0/24&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;198.51.100.0/24&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;203.0.113.0/24&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similarly, these address blocks are not intended for use in a production
network, but rather as placeholders for documentation and examples.&lt;/p&gt;
&lt;h2&gt;Proper Use of Reserved IP Addresses&lt;/h2&gt;
&lt;p&gt;Using reserved IP addresses for documentation and examples is essential
for a number of reasons. First, it ensures that these addresses are not
used for general Internet routing, which can help to prevent routing
issues and other network problems. Second, it helps to prevent confusion
and technical issues that can arise when non-reserved addresses are used
in documentation or example code.&lt;/p&gt;
&lt;p&gt;In recent years, there have been cases where non-reserved IP addresses
have been used in documentation and examples, leading to confusion and
technical issues. In a recent YouTube video, Cloudflare&apos;s Tom Strickx
highlighted this problem and encouraged operators to avoid addresses such
as &lt;code&gt;1.1.1.1&lt;/code&gt; and &lt;code&gt;1.2.3.4&lt;/code&gt;, using the ranges specified in RFC5735,
RFC5737, and RFC3849 instead.&lt;/p&gt;
&lt;div class=&quot;embed-wrapper&quot;&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=SIb_wxO1wt0&quot; class=&quot;embed-card&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=SIb_wxO1wt0&quot;&gt;https://www.youtube.com/watch?v=SIb_wxO1wt0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using reserved IP addresses in documentation and examples avoids
unintended traffic to real networks and improves clarity for readers.
RFC3849 and RFC5737 provide the standard address ranges for these use
cases, and network operators and researchers should use them consistently
in examples and documentation.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>RFC9234 Observed in the Wild</title><link>https://bgpkit.com/blog/rfc9234-observed-in-the-wild/</link><guid isPermaLink="true">https://bgpkit.com/blog/rfc9234-observed-in-the-wild/</guid><description>A look at RFC 9234 BGP roles and Only-to-Customer route-leak prevention observed in public BGP data.</description><pubDate>Thu, 16 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href=&quot;https://rfc.hashnode.dev/rfc9234-observed-in-the-wild&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;BGP Route Leaks&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Route leaks&lt;/strong&gt; occur when BGP prefixes are propagated in a way that
goes against the expected topology relationships of BGP. For example,
this can happen when a route learned from one transit provider is
announced to another transit provider or a lateral peer
(peer-peer-peer), or when a route learned from one lateral peer is
announced to another lateral peer or a transit provider (see
&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7908&quot; target=&quot;_blank&quot;&gt;RFC7908&lt;/a&gt;). These leaks often result from
misconfiguration or the absence of BGP route filtering, or from
inadequate coordination between autonomous systems (ASes).&lt;/p&gt;
&lt;p&gt;Cloudflare Radar includes a public &lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar/&quot; target=&quot;_blank&quot;&gt;route-leak detection system&lt;/a&gt;. The system detects
potential route leaks by first inferring inter-AS relationships on a
per-prefix basis, then examining each announced AS path for valley-free
violations. Detection is useful for visibility, but prevention is the
stronger operational goal.&lt;/p&gt;
&lt;h2&gt;RFC9234&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/rfc9234/&quot; target=&quot;_blank&quot;&gt;RFC9234&lt;/a&gt; documents an active route-leak prevention
approach where it defines new BGP capacities (&lt;code&gt;BGP Roles&lt;/code&gt;) exchanged
during the eBGP session open time and allows the BGP routers to
understand AS relationships between local and remote ASes, and thus
prevent the propagation of route leaks. RFC9234 also defines a new BGP
attributes type, &lt;code&gt;only-to-customer&lt;/code&gt;, which tells the receiving BGP
routers whether some routes should never be announced to another
provider.&lt;/p&gt;
&lt;p&gt;When a pair of eBGP routers both implement RFC9234, they will first
confirm the BGP role of each other.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9234.html#section-4.2&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://bgpkit.com/blog-assets/rfc-hashnode/rfc9234-observed-in-the-wild/image-1.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With roles defined, it handles the only-to-customer attributes as
follows.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9234.html#section-5&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://bgpkit.com/blog-assets/rfc-hashnode/rfc9234-observed-in-the-wild/image-2.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In essence, RFC9234 uses BGP roles and OTC attributes to make sure
&lt;strong&gt;routes received from a provider or a peer can only be propagated to
customers&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Only-to-customer Attribute in the Wild&lt;/h2&gt;
&lt;p&gt;RFC9234 was published in May 2022. Public discussion of RFC9234
deployment has been limited, and public measurements of deployment on
the Internet remain relatively scarce.&lt;/p&gt;
&lt;p&gt;To measure RFC9234 deployment, &lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/pull/65&quot; target=&quot;_blank&quot;&gt;RFC9234 support&lt;/a&gt; was added to
&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser&quot; target=&quot;_blank&quot;&gt;BGPKIT
Parser&lt;/a&gt;. The repository also includes &lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/blob/main/bgpkit-parser/examples/only-to-customer.rs&quot; target=&quot;_blank&quot;&gt;example code&lt;/a&gt; demonstrating how to parse a RIB file
and identify messages that contain Only-to-Customer attributes.&lt;/p&gt;
&lt;p&gt;The following output comes from parsing a single &lt;code&gt;route-views2&lt;/code&gt; RIB dump (&lt;a href=&quot;http://archive.routeviews.org/bgpdata/2023.03/RIBS/rib.20230316.0200.bz2&quot; target=&quot;_blank&quot;&gt;file link&lt;/a&gt;).&lt;/p&gt;
&lt;div class=&quot;gist-block embed-wrapper&quot; gist-show-loading=&quot;false&quot; data-id=&quot;756bd6b4d6627bf1e2a0a4f87a1c8290&quot;&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/digizeph/756bd6b4d6627bf1e2a0a4f87a1c8290&quot; class=&quot;embed-card&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://gist.github.com/digizeph/756bd6b4d6627bf1e2a0a4f87a1c8290&quot;&gt;https://gist.github.com/digizeph/756bd6b4d6627bf1e2a0a4f87a1c8290&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This output shows that at least four ASes (&lt;code&gt;AS6939&lt;/code&gt;, &lt;code&gt;AS15562&lt;/code&gt;,
&lt;code&gt;AS20555&lt;/code&gt;, &lt;code&gt;AS212068&lt;/code&gt;) were associated with RFC9234 OTC attributes in
messages that propagated to the route collector and were preserved in MRT
files.&lt;/p&gt;
&lt;p&gt;The code that generates this result iterates over parsed BGP messages
and checks whether the &lt;code&gt;only_to_customer&lt;/code&gt; attribute is present.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use bgpkit_parser::BgpkitParser;

fn main() {
    for elem in BgpkitParser::new(
        &quot;http://archive.routeviews.org/bgpdata/2023.03/RIBS/rib.20230316.0200.bz2&quot;,
    )
    .unwrap()
    {
        if let Some(otc) = elem.only_to_customer {
            println!(
                &quot;OTC found: {} for path {}\n{}\n&quot;,
                &amp;#x26;otc,
                &amp;#x26;elem.as_path.as_ref().unwrap(),
                &amp;#x26;elem
            );
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To reproduce this example, install the
&lt;a href=&quot;https://rustup.rs/&quot; target=&quot;_blank&quot;&gt;Rust toolchain&lt;/a&gt; and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/bgpkit/bgpkit-parser
cd bgpkit-parser
cargo run --release --example only-to-customer
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;Update, Mar. 16, 2023: Job Snijders provided additional context on the
source of these OTC attributes: &lt;a href=&quot;https://yycix.ca/&quot; target=&quot;_blank&quot;&gt;YYCIX&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;embed-wrapper&quot;&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/JobSnijders/status/1636291640519282688&quot; class=&quot;embed-card&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://twitter.com/JobSnijders/status/1636291640519282688&quot;&gt;https://twitter.com/JobSnijders/status/1636291640519282688&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>2022 Year in Review</title><link>https://bgpkit.com/blog/2022-year-in-review/</link><guid isPermaLink="true">https://bgpkit.com/blog/2022-year-in-review/</guid><description>In 2022, BGPKIT as an open-source organization made significant progresses. As the founder, I am grateful for all the opportunities would like to take this time...</description><pubDate>Tue, 31 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2022, BGPKIT as an open-source organization made significant progresses. As the founder, I am grateful for all the opportunities would like to take this time to appreciate all the milestones we achieved. In this post, I will go through some notable changes we made in 2022, and take a look at what we are excited about for the year of 2023.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;BGPKIT Parser&lt;/h2&gt;
&lt;p&gt;There were a number of major features added to BGPKIT Parser in 2022:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.7.0&quot;&gt;v0.7.0&lt;/a&gt; added support for filtering messages by many fields, and allowing reading from uncompressed files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.7.1&quot;&gt;v0.7.1&lt;/a&gt; added better examples of parallel MRT files processing with &lt;code&gt;rayon&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.7.2&quot;&gt;v0.7.2&lt;/a&gt; added filtering by multiple &lt;code&gt;peer_ip&lt;/code&gt;s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/releases/tag/v0.7.1&quot;&gt;v0.8.0&lt;/a&gt; includes many internal refactoring and brought in the new &lt;a href=&quot;https://github.com/bgpkit/oneio&quot;&gt;oneio&lt;/a&gt; library to improve developer experience.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;BGPKIT Broker&lt;/h2&gt;
&lt;p&gt;In 2022, we have revised the BGPKIT Broker backend to support crawling for estimated file sizes in addition to other fields like timestamps and URLs. This allows us to keep track of MRT file size changes and help users to pick suitable collectors to use, especially at the age where RIB dumps from a single collector could reach over 1 GB in size.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/rib-sizes-chart.Dc17U3jx.png&quot; alt=&quot;2022 Year in Review&quot;&gt;
&lt;p&gt;Figure of RIB sizes over time. Blue line is for rrc00, while yellow line is for route-views2.&lt;/p&gt;
&lt;p&gt;We also made major revisions to our Rust SDK for more features like &lt;code&gt;.latest()&lt;/code&gt; to get the lastest MRT files from each collector.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker/releases/tag/v0.5.0&quot;&gt;https://github.com/bgpkit/bgpkit-broker/releases/tag/v0.5.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Plus, if you would like to deploy a broker instance yourself, we also have made some significant efforts to improve our documentation on self-hosting guide.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker-backend/blob/main/deployment/README.md&quot;&gt;https://github.com/bgpkit/bgpkit-broker-backend/blob/main/deployment/README.md&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Python Bindings&lt;/h2&gt;
&lt;p&gt;In addition to core Rust code base for parser and broker, we have also added support for Python bindings for various Rust SDKs. Users can easily parse MRT files directly using &lt;code&gt;pybgpkit&lt;/code&gt; Python library. It is also proven to be usable on cloud-based Jupyter notebooks like Google Colab (&lt;a href=&quot;https://colab.research.google.com/drive/1AuNnzT43LYAZNnp1muhJTy0rvv3qbCX6&quot;&gt;examples&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/pybgpkit&quot;&gt;https://github.com/bgpkit/pybgpkit&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;monocle&lt;/h2&gt;
&lt;p&gt;To ties things together, we have also developed our first investigative tool, &lt;code&gt;monocle&lt;/code&gt; , to help users to quick find relevant BGP announcements with a suite of easy-to-use utilities. Users with Rust toolchain installed can run &lt;code&gt;cargo install monocle&lt;/code&gt; to install the tool.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/monocle&quot;&gt;https://github.com/bgpkit/monocle&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Users can use the following subcommands&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;parse&lt;/code&gt;: parse single MRT files, remotely or locally&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;search&lt;/code&gt;: find and filter BGP messages accross multiple public collectors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;time&lt;/code&gt;: convert between local time string and Unix timestamps&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;whois&lt;/code&gt;: find out AS names, ASN, registration countries, and organizations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Web/API and Infrastructure&lt;/h2&gt;
&lt;p&gt;In 2022, we started to experiment new cloud-based infrastructure, especially cloud-based databases for better API stability and developer experiences. We ended up selecting &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt; as our PostgreSQL production host and a self-hosted instance for backup. We are happy with the performance and cost provided by Supabase and more exicted about the potential capability it brings such as user authentication, cloud storage, local dev schema changes, etc.&lt;/p&gt;
&lt;p&gt;Based on the new infrastructure, we have started to test a new integrated API system (&lt;a href=&quot;https://alpha.api.bgpkit.com/docs/&quot;&gt;still in alpha&lt;/a&gt;). This allows us to put all of our data access and processing end-points into one location. Based on the new API, we have also developed a newer version of the BGPKIT Broker statistics page: &lt;a href=&quot;https://alpha.stats.bgpkit.com/&quot;&gt;https://alpha.stats.bgpkit.com/&lt;/a&gt;&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/alpha-api-stats.B5n3591r.png&quot; alt=&quot;2022 Year in Review&quot;&gt;
&lt;h2&gt;New Datasets&lt;/h2&gt;
&lt;p&gt;Apart from provide SDKs and data APIs, we have also started providing free access to historical archives of some data that we find interesting. We blogged about one dataset previous, the peer-stats dataset:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bgpkit.com/blog/introducing-peer-stats-dataset&quot;&gt;/blog/introducing-peer-stats-dataset&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is a list of our currently available datasets at &lt;a href=&quot;https://data.bgpkit.com/&quot;&gt;https://data.bgpkit.com/&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;[peer-stats](https://data.bgpkit.com/peer-stats/)&lt;/code&gt;: route collector peers statistics (IP, ASN, v4/v6 prefixes counts)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;[as2rel](https://data.bgpkit.com/as2rel/)&lt;/code&gt;: AS-level relationship, using all available collectors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;[pfx2as](https://data.bgpkit.com/pfx2as/)&lt;/code&gt;: prefix-to-AS mapping, using all available collectors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;[ihr-hegemony](https://data.bgpkit.com/ihr/hegemony/ipv4/global/)&lt;/code&gt;: mirror of &lt;a href=&quot;https://ihr.iijlab.net/ihr/en-us&quot;&gt;IIJ-IHR&lt;/a&gt;&apos;s global hegemony score dataset (big shout out to &lt;a href=&quot;https://twitter.com/romain_fontugne&quot;&gt;Romain&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/ihr_alerts&quot;&gt;Internet Health Report&lt;/a&gt; for producing this data)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the above datasets are free to use for research or commercial usages, and here is the &lt;a href=&quot;https://bgpkit.com/aua&quot;&gt;acceptable usage agreement&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;More Public Repositories&lt;/h2&gt;
&lt;p&gt;There are more open code repositories set available, some experimental and some for data analysis. You can check out the full list here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/orgs/bgpkit/repositories&quot;&gt;https://github.com/orgs/bgpkit/repositories&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Founder&apos;s Notes&lt;/h2&gt;
&lt;p&gt;In later 2022, I have joined Cloudflare to continue working on routing security for public benefits. During the first few months, we have built and shipped our &lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar/&quot;&gt;new route-leak detection system&lt;/a&gt; under the public &lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar/&quot;&gt;Radar&lt;/a&gt; platform. BGPKIT suite is now used in production for the BGP data anaysis pipeline at Cloudflare. While working full-time now, I am still committed to maintain the software suite and bringing new features to BGPKIT. For example, this year, I ported back our Kafka support used in Cloudflare to BGPKIT Broker backend. Folks at Cloudflare are doing great things in the open-source realm, and BGPKIT software suite will continue to become more useful to BGP enthuesastics and remain completely open-source.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/cloudflare-route-leak.B-cDL-17.png&quot; alt=&quot;2022 Year in Review&quot;&gt;
&lt;p&gt;Quote from Cloudflare&apos;s &lt;a href=&quot;https://blog.cloudflare.com/route-leak-detection-with-cloudflare-radar&quot;&gt;route-leak detection system blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at 2023, here are a few things that I am really excited to work on for BGPKIT suite:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;continue improve the infrastructure of the system and adding new data processing pipelines&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;continue improve the parser&apos;s performance and reliability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;adding new RFC supports to parser (e.g. &lt;a href=&quot;https://datatracker.ietf.org/doc/rfc9234/&quot;&gt;RFC9234&lt;/a&gt; for route-leak prevention)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;productionizing the new API and stats website&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;write more examples and documentations (don&apos;t we all love that)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And yeah, we will continue to be open-source first!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bgpkit.com&quot;&gt;&lt;img src=&quot;https://bgpkit.com/_astro/bgpkit-website.Btyt9Ekl.png&quot; alt=&quot;2022 Year in Review&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you like what we do here, please consider subscribe to our blog. For all code repositories, check out our GitHub page.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit&quot;&gt;https://github.com/bgpkit&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Introducing Peer-Stats Dataset</title><link>https://bgpkit.com/blog/introducing-peer-stats-dataset/</link><guid isPermaLink="true">https://bgpkit.com/blog/introducing-peer-stats-dataset/</guid><description>Public BGP data collector projects like RouteViews and RIPE RIS provide valuable research and operational information for understanding BGP and detecting Intern...</description><pubDate>Mon, 16 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Public BGP data collector projects like RouteViews and RIPE RIS provide valuable research and operational information for understanding BGP and detecting Internet routing anomalies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There are many BGP routers involved in BGP collection projects.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A project includes many &quot;collectors,&quot; and each serves as a collection of messages from several active BGP peers from different networks. Some bigger collectors collect BGP data from more than one hundred BGP routers. For example, RIPE RIS &lt;code&gt;rrc00&lt;/code&gt;   has 112 active BGP peers at writing. &lt;em&gt;(To learn about the complete list of BGP peers from all collectors, try the&lt;/em&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-labs/tree/main/collector-peers&quot;&gt;&lt;em&gt;experimental tools&lt;/em&gt;&lt;/a&gt;&lt;em&gt;we developed.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes, too many BGP peers may become problematic.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not all peers present the same amount of data. Some peers are so-called &quot;full-feed&quot; peers, which are the ones that provide the full routing tables to the collector. In a routing table dump file from the collectors, we can observe the full table of these peers. Some peers, however, only provide a limited number of routing entries to the collectors, not representing the whole routing status from these peers. In a project that tries to rebuild full routing tables, e.g., some BGP hijack detection or anomaly detectors, people prefer to use the full-feed peers as their data source.&lt;/p&gt;
&lt;p&gt;At times, we are only interested in data from certain peers. For example, when studying the routing data from a particular network, if the network connects to BGP data collectors, we can directly pull data from the collectors&apos; data. However, it can be troublesome to learn about what collectors have data from certain peers. RIPE RIS provides a &lt;a href=&quot;https://stat.ripe.net/data/ris-peers/data.json&quot;&gt;nice API&lt;/a&gt; for querying such info, but we couldn&apos;t find one for RouteViews.&lt;/p&gt;
&lt;p&gt;Historical data for such information is also missing. Unfortunately, for the researchers who want to study the evolution of the data collectors, even RIPE RIS&apos;s peers API could not help with that.&lt;/p&gt;
&lt;h2&gt;Introducing BGPKIT Peer-Stats Dataset&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Peer-Stats&lt;/code&gt; dataset is a publicly available, free-to-use dataset that aims to provide daily collector peer information for all RouteViews and RIPE RIS collectors for ten years.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://data.bgpkit.com/peer-stats/&quot;&gt;https://data.bgpkit.com/peer-stats/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The data includes the following fields for each peer of a BGP collector:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;asn&lt;/code&gt;: Autonomous System Number of the collector peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ip&lt;/code&gt;: the IP address of the collector peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;num_v4_pfxs&lt;/code&gt;: the number of IPv4 prefixes propagated from the collector peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;num_v6_pfx&lt;/code&gt;s: the number of IPv6 prefixes propagated from the collector peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;num_connected_asns&lt;/code&gt;: the number of connected (immediate next hop) ASes from the collector peer&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The dataset is organized by the following structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;- collector
	- year
		- month
			- data files
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/dataset-listing-1.Cr1ixJRg.png&quot; alt=&quot;Introducing Peer-Stats Dataset&quot;&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/dataset-listing-2.DUTCb5_q.png&quot; alt=&quot;Introducing Peer-Stats Dataset&quot;&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/dataset-listing-3.DP6D80fo.png&quot; alt=&quot;Introducing Peer-Stats Dataset&quot;&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/dataset-listing-4.C76KweMC.png&quot; alt=&quot;Introducing Peer-Stats Dataset&quot;&gt;
&lt;p&gt;Screenshots of the dataset file listing site.&lt;/p&gt;
&lt;p&gt;Each data file is in JSON format (see the section below) and compressed with bzip2. Users can easily use tools like &lt;code&gt;bzcat&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; to view the data files. For example, you can run the following command to quickly view any of the peer-stats data for the collector &lt;code&gt;rrc00&lt;/code&gt; on 2022-05-01.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl &quot;https://data.bgpkit.com/peer-stats/rrc00/2022/05/rrc00-2022-05-01-1651363200.bz2&quot; --silent | bzcat | jq
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;collector&quot;: &quot;rrc00&quot;,
  &quot;peers&quot;: {
    &quot;102.67.56.1&quot;: {
      &quot;asn&quot;: 328474,
      &quot;ip&quot;: &quot;102.67.56.1&quot;,
      &quot;num_connected_asns&quot;: 330,
      &quot;num_v4_pfxs&quot;: 919443,
      &quot;num_v6_pfxs&quot;: 0
    },
    &quot;103.102.5.1&quot;: {
      &quot;asn&quot;: 131477,
      &quot;ip&quot;: &quot;103.102.5.1&quot;,
      &quot;num_connected_asns&quot;: 184,
      &quot;num_v4_pfxs&quot;: 895482,
      &quot;num_v6_pfxs&quot;: 0
    },
...
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/dataset-listing-5.D-gdUTTB.png&quot; alt=&quot;Introducing Peer-Stats Dataset&quot;&gt;
&lt;p&gt;Because all the data files are generated against the midnight UTC RIB dump of the day, you can also easily construct a URL to a data file for any particular date using the following template.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://data.bgpkit.com/peer-stats/{COLLECTOR}/{YEAR}/{MONTH}/{COLLECTOR}-{YEAR}-{MONTH}-{DAY}-{MIDNIGHT_TIMESTAMP}.bz2&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Open-source&lt;/h2&gt;
&lt;p&gt;We also open-sourced the data collection command-line tool source code on GitHub. Feel free to check it out and run it on your infrastructure if needed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/peer-stats&quot;&gt;https://github.com/bgpkit/peer-stats&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Credits and Sponsorship&lt;/h2&gt;
&lt;p&gt;The original idea for this work came from our extensive discussion with Romain Fontugne (follow him on Twitter at &lt;a href=&quot;https://twitter.com/romain_fontugne&quot;&gt;@romain_fontugne&lt;/a&gt;) from IIJ. This work is made possible by IIJ&apos;s generous sponsorship.&lt;/p&gt;
&lt;p&gt;Please consider sponsoring us on GitHub if you find our work valuable and would like to see more open-source code and datasets on BGP.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/sponsors/bgpkit&quot;&gt;https://github.com/sponsors/bgpkit&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>KhersonTelecom Outage and Connectivity Change</title><link>https://bgpkit.com/blog/2022-05-01-khersontelecom-connectivity-change/</link><guid isPermaLink="true">https://bgpkit.com/blog/2022-05-01-khersontelecom-connectivity-change/</guid><description>KhersonTelecom service outage and upstream provider change during the Ukraine-Russia conflict in April 2022.</description><pubDate>Tue, 03 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/tweet-outage-announcement.BvZddyRq.png&quot; alt=&quot;KhersonTelecom Outage and Connectivity Change&quot;&gt;
&lt;p&gt;Internet service in Russian-occupied Kherson, Ukraine was disabled at 16:12 UTC (6:12pm local) on Saturday, 30 April. &lt;a href=&quot;https://twitter.com/hashtag/UkraineRussiaWar?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#UkraineRussiaWar&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Khersontelecom service was restored ~24hrs later via Russian transit from nearby Crimea. &lt;a href=&quot;https://t.co/uN31jLrzEc&quot;&gt;pic.twitter.com/uN31jLrzEc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;— Doug Madory (@DougMadory) &lt;a href=&quot;https://twitter.com/DougMadory/status/1521102562509873152?ref_src=twsrc%5Etfw&quot;&gt;May 2, 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;AS47598&lt;/code&gt; experienced an outage shortly after &lt;code&gt;2022-04-30T16:10:00&lt;/code&gt; and then resumed connectivity &lt;code&gt;2022-05-01T16:15:00&lt;/code&gt; (both UTC time). After the outage, the &lt;code&gt;AS47598&lt;/code&gt; is then connected via a different upstream provider, &lt;code&gt;AS201776&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The upstream provide change can also be seen on IIJ&apos;s &lt;a href=&quot;https://ihr.iijlab.net/ihr/en-us/networks/AS47598?af=4&amp;#x26;last=3&amp;#x26;date=2022-05-01&amp;#x26;rov_tb=routes&quot;&gt;Internet Health Report&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/iij-internet-health-report.BU8PF1Sb.png&quot; alt=&quot;KhersonTelecom Outage and Connectivity Change&quot;&gt;
&lt;h2&gt;Prefixes&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AS47598&lt;/code&gt; announces only one prefix &lt;code&gt;91.206.110.0/23&lt;/code&gt; (data from &lt;a href=&quot;https://bgp.he.net/AS47598#_prefixes&quot;&gt;Hurricane Electric&lt;/a&gt;). Most of the BGP announcements are for this prefix. However, after the provider change happened, there were also a IPv6 prefix announcements for this V6 prefix as well &lt;code&gt;5bce:6e00::/23&lt;/code&gt;. It is possible that this prefix is a V4-translated prefix propagated to a V6 collector peer, but we do not know for sure.&lt;/p&gt;
&lt;p&gt;We can also confirm the outage of the prefix with RIPEstat’s &lt;a href=&quot;https://stat.ripe.net/widget/routing-history#w.resource=91.206.110.0/23&amp;#x26;w.starttime=2022-04-25T00:00:00&amp;#x26;w.endtime=2022-05-02T00:00:00&quot;&gt;Routing History data widget&lt;/a&gt;. (uncheck the &lt;code&gt;No low visibility&lt;/code&gt; box to reveal the outage).&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/ripestat-routing-history.BNkMrZur.png&quot; alt=&quot;KhersonTelecom Outage and Connectivity Change&quot;&gt;
&lt;h2&gt;BGP Messages&lt;/h2&gt;
&lt;p&gt;We can visualize the overall BGP announcements volume with &lt;a href=&quot;https://radar.cloudflare.com/asn/47598?date_filter=last_7_days&quot;&gt;Cloudflare Radar’s AS-level page&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/cloudflare-radar-bgp.C8p3hu1S.png&quot; alt=&quot;KhersonTelecom Outage and Connectivity Change&quot;&gt;
&lt;p&gt;The following are the UTC timestamps for the corresponding BGP message spikes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2022-04-30T16:10:00&lt;/code&gt;: announcements with old provider &lt;code&gt;12883&lt;/code&gt; in the paths.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2022-05-01T16:00:00&lt;/code&gt;: announcements with the new provider &lt;code&gt;201776&lt;/code&gt; in the paths&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2022-05-03T10:45:00&lt;/code&gt;: similar announcements with a new provider in the paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During the first gap time (2022-04-30T16:15:00 to 2022-05-01T16:00:00), there were 0 BGP updates for the prefix or from the ASN.&lt;/p&gt;
&lt;p&gt;The old provider paths look like this where &lt;code&gt;AS12883&lt;/code&gt; is the next hop for &lt;code&gt;AS47598&lt;/code&gt;. See the full list of messages from &lt;code&gt;rrc00&lt;/code&gt; here: &lt;a href=&quot;https://gist.github.com/digizeph/c58b77f755d7fec8a7969807fb17d5ba&quot;&gt;https://gist.github.com/digizeph/c58b77f755d7fec8a7969807fb17d5ba&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;207564 56655 3257 12883 47598
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new provider paths look like this where &lt;code&gt;AS12389&lt;/code&gt; and &lt;code&gt;AS201776&lt;/code&gt; are the next hops for &lt;code&gt;AS47598&lt;/code&gt;. See the full list of messages from &lt;code&gt;rrc00&lt;/code&gt; here: &lt;a href=&quot;https://gist.github.com/digizeph/896a4a7e4de23082b496b92ab5bdab5b&quot;&gt;https://gist.github.com/digizeph/896a4a7e4de23082b496b92ab5bdab5b&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;207564 28824 28824 1299 12389 201776 47598
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;BGP Data Tooling&lt;/h2&gt;
&lt;p&gt;The analysis is done using a privately hosted open-source BGPKIT parser web API. You can host it on your infrastructure, and the source code is freely available at &lt;a href=&quot;https://github.com/bgpkit/pybgpkit-api&quot;&gt;https://github.com/bgpkit/pybgpkit-api&lt;/a&gt;. Comments and feedback are welcome!&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Update on 2022-05-04T09:20:00 Pacific&lt;/h3&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/tweet-connectivity-update.Nvge0DRJ.png&quot; alt=&quot;KhersonTelecom Outage and Connectivity Change&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/hashtag/Kherson?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#Kherson&lt;/a&gt; — Internet connectivity is returning to the occupied city in South of &lt;a href=&quot;https://twitter.com/hashtag/Ukraine?src=hash&amp;#x26;ref_src=twsrc%5Etfw&quot;&gt;#Ukraine&lt;/a&gt;, after an outage since Saturday. &lt;a href=&quot;https://twitter.com/Cloudflare?ref_src=twsrc%5Etfw&quot;&gt;@Cloudflare&lt;/a&gt; data shows growth in requests since 04:15 UTC, and telecom connection was confirmed by the Ukrainian Vice PM &lt;a href=&quot;https://twitter.com/FedorovMykhailo?ref_src=twsrc%5Etfw&quot;&gt;@FedorovMykhailo&lt;/a&gt;. &lt;a href=&quot;https://t.co/JxT3kcM234&quot;&gt;pic.twitter.com/JxT3kcM234&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;— Cloudflare Radar (@CloudflareRadar) &lt;a href=&quot;https://twitter.com/CloudflareRadar/status/1521812037055176705?ref_src=twsrc%5Etfw&quot;&gt;May 4, 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The upstreams for &lt;code&gt;AS47598&lt;/code&gt; has reverted back to the original ASes, and the traffic has started coming back to normal.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Parallel MRT Files Parsing with BGPKIT</title><link>https://bgpkit.com/blog/parallel-mrt-files-parsing-with-bgpkit/</link><guid isPermaLink="true">https://bgpkit.com/blog/parallel-mrt-files-parsing-with-bgpkit/</guid><description>In this post, we will talk about how to implement a Rust workflow that can process a large number of BGP data files as fast as we can. We will use BGPKIT Parser...</description><pubDate>Wed, 16 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post, we will talk about how to implement a Rust workflow that can process a &lt;strong&gt;large number&lt;/strong&gt; of BGP data files &lt;strong&gt;as fast as we can&lt;/strong&gt;. We will use BGPKIT &lt;a href=&quot;https://bgpkit.com/parser&quot;&gt;Parser&lt;/a&gt; and &lt;a href=&quot;https://bgpkit.com/broker&quot;&gt;Broker&lt;/a&gt; for data collection and parsing, and &lt;a href=&quot;https://github.com/rayon-rs/rayon&quot;&gt;Rayon&lt;/a&gt; crate for parallelization of the code.&lt;/p&gt;
&lt;h2&gt;Task Overview&lt;/h2&gt;
&lt;p&gt;Before we begin to talk about the code design, we first need to introduce the data we are dealing with. We want to process the BGP data collected by various collectors, saved in compressed binary MRT format, and archived to files with a fixed interval. In this post, we are using &lt;a href=&quot;https://archive.routeviews.org/&quot;&gt;RouteViews&lt;/a&gt; archive data as an example. The average data file size ranges from 2MB to 10MB by different collectors (&lt;a href=&quot;https://archive.routeviews.org/route-views.amsix/bgpdata/2021.10/UPDATES/&quot;&gt;AMSIX collector&lt;/a&gt; for example has pretty large files).&lt;/p&gt;
&lt;p&gt;For processing, we can use the simplest task possible to do: &lt;em&gt;sum the number of MRT records in all the files&lt;/em&gt;. We want to download and process all the updates files for one hour from all the collectors in RouteViews project. Here is the estimated amount of data we are dealing with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;35 collectors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5-minute interval — 12 files per collector&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;420 total number of files&lt;/strong&gt; to download and process&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;840MB to 4.2GB total download size&lt;/strong&gt; (it’s somewhere in between)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ok! Now that we know what we want to do and have a sense of the estimated workload of the overall task, let’s coding!&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/header-parallel.CKcRAIk8.jpeg&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@glenncarstenspeters?utm_source=medium&amp;#x26;utm_medium=referral&quot;&gt;Glenn Carstens-Peters&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/?utm_source=medium&amp;#x26;utm_medium=referral&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. Sequential Parsing&lt;/h2&gt;
&lt;p&gt;Our first attempt to achieve the goal is to design and implement a naive sequential workflow as described below&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;find all BGP updates files within the hour of interest&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iterate through each file, parse the MRT data and count the number of records&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sum all record counts and print out the result&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For this sequential workflow, we will need to pull in two dependencies into &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[dependencies]
bgpkit-parser = &quot;0.7.2&quot;
bgpkit-broker = &quot;0.3.2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;bgpkit-broker&lt;/code&gt; handles looking for updates files within the hour, while &lt;code&gt;bgpkit-parser&lt;/code&gt; handles parsing each individual file.&lt;/p&gt;
&lt;h3&gt;Finding files&lt;/h3&gt;
&lt;p&gt;BGPKIT Broker indexes all available BGP MRT data archive files from both RouteViews and RIPE RIS in close-to-real-time. For each data file, it saves the following information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;project&lt;/code&gt;: &lt;code&gt;route-views&lt;/code&gt; or &lt;code&gt;riperis&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;collector&lt;/code&gt;: the collector ID, e.g. &lt;code&gt;rrc00&lt;/code&gt; or &lt;code&gt;route-views2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;url&lt;/code&gt;: the URL to the corresponding MRT file&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;timestamp&lt;/code&gt;: the UNIX time of the &lt;em&gt;start time&lt;/em&gt; of the MRT data file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all this information indexed, we can then query the backend and retrieve files information as we want. BGPKIT Broker provides both &lt;a href=&quot;https://docs.broker.bgpkit.com&quot;&gt;RESTful API&lt;/a&gt;, &lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker&quot;&gt;Rust API&lt;/a&gt;, as well as a &lt;a href=&quot;https://pypi.org/project/pybgpkit/&quot;&gt;Python API&lt;/a&gt;. Here we use the Rust API to pull in the information we need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let broker = BgpkitBroker::new_with_params(
    &quot;https://api.broker.bgpkit.com/v1&quot;,
    QueryParams {
        start_ts: Some(1640995200),
        end_ts: Some(1640998799),
        project: Some(&quot;route-views&quot;.to_string()),
        data_type: Some(&quot;update&quot;.to_string()),
        ..Default::default()
    });

for item in &amp;#x26;broker {
    println!(&quot;processing {:?}...&quot;, &amp;#x26;item);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above block queries the broker and prints out all information of the retrieved files&apos; metadata. The &lt;code&gt;BgpkitBroker::new_with_params&lt;/code&gt; call accepts two parameters, one for the endpoint of the broker instance, and the other specifies the filtering criteria. In this example, we search for all BGP updates files from RouteViews with timestamps between &lt;code&gt;2022-01-01T00:00:00&lt;/code&gt; and &lt;code&gt;2022-01-01T00:59:59&lt;/code&gt; UTC. It prints out the output as the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;processing BrokerItem { collector_id: &quot;route-views.telxatl&quot;, timestamp: 1640997000, data_type: &quot;update&quot;, url: &quot;https://archive.routeviews.org/route-views.telxatl/bgpdata/2022.01/UPDATES/updates.20220101.0030.bz2&quot; }...
processing BrokerItem { collector_id: &quot;route-views.uaeix&quot;, timestamp: 1640997000, data_type: &quot;update&quot;, url: &quot;https://archive.routeviews.org/route-views.uaeix/bgpdata/2022.01/UPDATES/updates.20220101.0030.bz2&quot; }...
processing BrokerItem { collector_id: &quot;route-views.wide&quot;, timestamp: 1640997000, data_type: &quot;update&quot;, url: &quot;https://archive.routeviews.org/route-views.wide/bgpdata/2022.01/UPDATES/updates.20220101.0030.bz2&quot; }...
processing BrokerItem { collector_id: &quot;route-views2&quot;, timestamp: 1640997900, data_type: &quot;update&quot;, url: &quot;https://archive.routeviews.org/bgpdata/2022.01/UPDATES/updates.20220101.0045.bz2&quot; }...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Parse each MRT file&lt;/h3&gt;
&lt;p&gt;Previously, in the for loop, we only print out the retrieved meta information of the MRT files. Now let&apos;s add the actual parsing of the files into the loop. The code is very simple, as designed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut sum: usize = 0;
for item in &amp;#x26;broker {
    println!(&quot;processing {}...&quot;, &amp;#x26;item.url);
    let parser = BgpkitParser::new(&amp;#x26;item.url).unwrap();
    let count = parser.into_record_iter().count();
    sum += count;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We first define a mutable variable &lt;code&gt;sum&lt;/code&gt; outside the loop. Then for each file, we create a new Parser instance by &lt;code&gt;BgpkitParser::_new_(&amp;#x26;item.url)&lt;/code&gt;. Here, as our goal is to count the number of records, we call the parser&apos;s &lt;code&gt;.into_record_iter()&lt;/code&gt; function to create a iterator over the records of the file, and then &lt;code&gt;.count()&lt;/code&gt; to get the count of the records. Lastly, we add the count to the overall sum variable.&lt;/p&gt;
&lt;h3&gt;Run and timing&lt;/h3&gt;
&lt;p&gt;For testing, I use a fairly powerful VM on a host with AMD 3950x CPU (32 threads), then build the release build and time the release run. The runtime includes downloading the MRT files to my machine with 1Gpbs down link in Southern California.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo build --release
time cargo run --release --bin sequential
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It ended up taking about &lt;strong&gt;1 minute and 23 seconds&lt;/strong&gt; to sequentially parse 144 MRT files from RouteViews for all available ones within the first hour of 2022 (UTC).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;total number of records for 144 files is 10554212

real    1m23.081s
user    0m39.535s
sys     0m1.006s
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Parallel Parsing&lt;/h2&gt;
&lt;p&gt;Since the parsing of each file is completely independent of each other, we can parse the files in parallel and then sum up the count for each thread at the end. In Rust with Rayon, this conversion is very simple.&lt;/p&gt;
&lt;p&gt;Let&apos;s first add the dependency of Rayon first:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[dependencies]
bgpkit-parser = &quot;0.7.2&quot;
bgpkit-broker = &quot;0.3.2&quot;
rayon = &quot;1.5.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we change the broker code one tiny bit to collect all meta information for files into a vector first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let items = broker.into_iter().collect::&amp;#x3C;Vec&amp;#x3C;BrokerItem&gt;&gt;();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would enable us to fully utilize &lt;code&gt;rayon&lt;/code&gt;&apos;s great syntax sugar to turn our sequential code into a parallel one.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let sum: usize = items.par_iter().map(|item| {
    println!(&quot;processing {}...&quot;, &amp;#x26;item.url);
    let parser = BgpkitParser::new(&amp;#x26;item.url).unwrap();
    let count = parser.into_record_iter().count();
    count
}).sum();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key difference here is the calling of &lt;code&gt;.par_iter()&lt;/code&gt;. It turns a sequential iterator into a parallel iterator, and by default going to utilize all available cores on the host machine for scheduling. Then we call &lt;code&gt;.map()&lt;/code&gt; to define the parsing steps for each file, and then call &lt;code&gt;.sum()&lt;/code&gt; at the end to add all results up.&lt;/p&gt;
&lt;p&gt;The final result is approximately &lt;strong&gt;10x faster&lt;/strong&gt; than the sequential version, and it took only &lt;strong&gt;8 seconds&lt;/strong&gt; to parse all MRT files and get the record counts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;total number of records is 10554212

real    0m8.086s
user    0m42.569s
sys     0m1.068s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full code for this example is as follows:&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The source code of the two examples is available on GitHub. Feel free to poke around and tweak it as you wish.&lt;/p&gt;
&lt;p&gt;%[&lt;a href=&quot;https://gist.github.com/digizeph/c23ba39968c6cb4e1ad323520540010f#file-parallel-parsing-rs&quot;&gt;https://gist.github.com/digizeph/c23ba39968c6cb4e1ad323520540010f#file-parallel-parsing-rs&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-tutorials/tree/main/parallel-parsing&quot;&gt;https://github.com/bgpkit/bgpkit-tutorials/tree/main/parallel-parsing&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Real-time RIS Live Data with BGPKIT Parser</title><link>https://bgpkit.com/blog/real-time-bgp-data-processing-2-ris-live/</link><guid isPermaLink="true">https://bgpkit.com/blog/real-time-bgp-data-processing-2-ris-live/</guid><description>In terms of real-time BGP data processing, RIPE NCC provides a great data source: Routing Information Service Live (RIS Live).</description><pubDate>Fri, 12 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In terms of real-time BGP data processing, &lt;a href=&quot;https://www.ripe.net/&quot;&gt;RIPE NCC&lt;/a&gt; provides a great data source: &lt;a href=&quot;https://ris-live.ripe.net/&quot;&gt;Routing Information Service Live (RIS Live)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To begin with, here is what &lt;a href=&quot;https://ris-live.ripe.net/&quot;&gt;RIS Live&lt;/a&gt; by the creators:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RIS Live is a feed that offers BGP messages in real-time. It collects information from the RIS Route Collectors (RRCs) and uses a WebSocket JSON API to monitor and detect routing events around the world. A non-interactive full stream (“firehose”) is also available.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In essence, RIS Live provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a WebSocket interface to stream BGP messages in real-time&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ability to subscribe to “sub-streams” with custom filtering messages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JSON-encoded BGP messages as the stream payload&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“firehose” HTTPS stream interface as well, without needing to work with websocket.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, we will discuss how to use the RIS Live stream in practice.&lt;/p&gt;
&lt;h2&gt;RIS Live Message Format&lt;/h2&gt;
&lt;p&gt;RIS Live has &lt;em&gt;client messages&lt;/em&gt; and &lt;em&gt;server messages&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The client messages is used to setup or dismantle “subscriptions”, which essentially tell the server what kind of BGP messages a client would like to receive, and allow the server to send only the interested messages to the client.&lt;/p&gt;
&lt;p&gt;A server acknowledges the requests from the client and afterwards start streaming requested data back to the client. At a high-level, a server sends either &lt;code&gt;ris_message&lt;/code&gt; or &lt;code&gt;ris_error&lt;/code&gt; messages. The &lt;code&gt;ris_message&lt;/code&gt; is the main payload that we are interested in, while the &lt;code&gt;ris_error&lt;/code&gt; message provides debugging messages for the scenarios where stream or subscription fails.&lt;/p&gt;
&lt;h2&gt;Subscribe to a WebSocket Stream&lt;/h2&gt;
&lt;p&gt;RIS Live provides great flexibility for the clients to specify/narrowdown the interested messages, allowing both the server and client process less messages during a streaming session.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;host&lt;/code&gt;: only messages collected from a particular RRC (e.g. &lt;code&gt;rrc21&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt;: only messages of a given type, e.g. &lt;code&gt;UPDATE&lt;/code&gt; , &lt;code&gt;OPEN&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;require&lt;/code&gt; : only messages containing a given key, e.g. &lt;code&gt;withdrawals&lt;/code&gt; will return only message that contains any withdrawn prefixes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;peer&lt;/code&gt;: messages from a particular BGP peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;path&lt;/code&gt; : ASN or pattern to match the AS Path attribute in BGP update messages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;prefix&lt;/code&gt;: only messages containing information for a given prefix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;moreSpecific&lt;/code&gt; and &lt;code&gt;lessSpecific&lt;/code&gt;: only messages that are the subprefix or super-prefix of the specified prefix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;includeRaw&lt;/code&gt;: whether to include the Base64-encoded RAW BGP messages&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As an example, let’s take a look at the following message from the official manual:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;host&quot;: &quot;rrc01&quot;,
  &quot;type&quot;: &quot;UPDATE&quot;,
  &quot;require&quot;: &quot;announcements&quot;,
  &quot;path&quot;: &quot;64496,64497$&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example subscription message composer on &lt;a href=&quot;https://ris-live.ripe.net/&quot;&gt;RIS Live official site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As an example, let’s take a look at the following message from the official manual:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;host&quot;: &quot;rrc01&quot;,
  &quot;type&quot;: &quot;UPDATE&quot;,
  &quot;require&quot;: &quot;announcements&quot;,
  &quot;path&quot;: &quot;64496,64497$&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;collected by &lt;code&gt;rrc01&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BGP UPDATE messages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;have at least one announced prefix&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the last two hops of the AS Path is 66496 and 64497 (the origin)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;ris_message&lt;/code&gt; consists of “common header” fields and “data” fields (although they’re on the same level).&lt;/p&gt;
&lt;p&gt;The “common header” fields are present for all types of sub-type messages, including &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;peer&lt;/code&gt;, &lt;code&gt;peer_asn&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt; . The rest of the fields are the data fields that are dependent on the type of the messages. For most people, the &lt;code&gt;UPDATE&lt;/code&gt; message is what they need. The following JSON block is an example message pulled directly from the demo site.&lt;/p&gt;
&lt;p&gt;Example JSON formatted RIS message:&lt;/p&gt;
&lt;p&gt;This example shows a BGP announcement of AS132354 originating two prefixes &lt;code&gt;103.249.208.0/23&lt;/code&gt; and &lt;code&gt;103.14.184.0/24&lt;/code&gt; , with the next hop to be &lt;code&gt;37.49.237.228&lt;/code&gt;. At this point, the information we see here is pretty similar to what we can see from other BGP MRT reader’s output (e.g. from &lt;code&gt;bgpdump&lt;/code&gt; or &lt;code&gt;bgpreader&lt;/code&gt;), just in JSON format.&lt;/p&gt;
&lt;h2&gt;WebSocket or Firehose?&lt;/h2&gt;
&lt;p&gt;Provided that RIS Live provides both WebSocket and HTTP Firehose, one would naturally wonder which one is the right choice for their application. Here we have a brief comparison between the two in the context of RIS Live.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Good:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;easy to customize stream by composing a simple JSON subscribe message&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;work with various toolings in languages like Python and JavaScript&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;requires extra library dependencies to work with WebSocket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;need to write somewhat lengthy to get started (comparing to firehose)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Firehose&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Good:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;easy to consume by simply calling GET request on the URL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;simple single-liner commandline program can start the stream (e.g. a simple &lt;code&gt;curl&lt;/code&gt; call), no need complex script&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;customizing stream is doable with &lt;code&gt;XRIS-SUBSCRIBE&lt;/code&gt; HTTP request header, but feels clunky and limited&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;in my personal tests, the stream get disconnected often due to the stream cannot keep up with the data producer. this did not happen with websocket tests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;If your application could afford additional dependencies or writing extra scripts, WebSocket is the better choice. RIS Live official manual also makes implication that the WebSocket format is the current formally-supported streaming method.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;RIS Live Coding Example with BGPKIT Parser&lt;/h2&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/header-coding.DYyAPyqA.png&quot; alt=&quot;Person coding with MacBook Pro&quot;&gt;
&lt;p&gt;Now that we have a basic idea of what is RIS Live and the basic message format, we can get started working on some code that will actually use RIS Live to do something useful.&lt;/p&gt;
&lt;p&gt;In the following example, we will build a short monitoring service that alerts us when Facebook operators announces their DNS IP prefix (see what happened before &lt;a href=&quot;https://blog.cloudflare.com/october-2021-facebook-outage/&quot;&gt;here&lt;/a&gt;). We are going to build the service in Rust with &lt;a href=&quot;https://bgpkit.com/parser&quot;&gt;BGPKIT Parser&lt;/a&gt; , WebSocket library &lt;a href=&quot;https://github.com/snapview/tungstenite-rs&quot;&gt;Tungstenite&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, lets collect some basic information about what we are going to monitor here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Facebook’s autonomous system number is 32934. So we will watch for all messages that was originated from AS32934.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Facebook’s DNS server IP prefixes involved in the previous incidence are &lt;code&gt;129.134.30.0/23&lt;/code&gt; and &lt;code&gt;185.89.218.0/23&lt;/code&gt; . So we want to carefully watch these two prefixes in our monitoring system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We want to use one of the RIPE RIS collectors’ data for monitoring, &lt;code&gt;rrc21&lt;/code&gt; is a good choice since it’s being used by RIS Live’s demonstration. You can easily extend this service by tweaking the subscription message later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;OK, we are good to go. Let’s do it!&lt;/p&gt;
&lt;h2&gt;Setting up the stream&lt;/h2&gt;
&lt;p&gt;We picked the Tungstenite library as our WebSocket library of choice, partly because it has a very straightforward API design.&lt;/p&gt;
&lt;p&gt;Let’s first connect to the websocket server by calling &lt;code&gt;connect&lt;/code&gt; function given a websocket URL. One thing to notice is that the URL protocol section here is &lt;code&gt;ws&lt;/code&gt; as opposed to the &lt;code&gt;wss&lt;/code&gt; mentioned in the RIS Live documentation. For some reason, Tungstenite does not work with &lt;code&gt;wss&lt;/code&gt; protocol (with SSL).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use tungstenite::{connect, Message}; 
const RIS_LIVE_URL: &amp;#x26;str = &quot;ws://ris-live.ripe.net/v1/ws/?client=rust-bgpkit-parser&quot;;
let (mut socket, _response) =
    connect(Url::parse(RIS_LIVE_URL).unwrap())
    .expect(&quot;Can&apos;t connect to RIS Live websocket server&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, with a socket ready, we will first send a subscription message to let server know that we want some messages and we are ready to receive.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let msg = json!({&quot;type&quot;: &quot;ris_subscribe&quot;, &quot;data&quot;: {&quot;host&quot;: &quot;rrc21&quot;}}).to_string();
socket.write_message(Message::Text(msg)).unwrap();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we composed a simple subscription message that limits the stream to have messages only from &lt;code&gt;rrc21&lt;/code&gt; collector.&lt;/p&gt;
&lt;h2&gt;Parsing JSON messages&lt;/h2&gt;
&lt;p&gt;At this point, we have a WebSocket connection to RIS Live server, and have sent out a subscription message to the server. The server should be sending back messages anytime now, and we are ready to consume the stream.&lt;/p&gt;
&lt;p&gt;We would like to code the following behavior:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;continuously reading the websocket messages;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;parse JSON string into internal BGP structs;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;check each message if it contains origins (withdraw-only messages does not contain AS paths, and thus no origins either;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if the origin AS is AS32934, and the announced prefix is &lt;code&gt;129.134.30.0/23&lt;/code&gt; or &lt;code&gt;185.89.218.0/23&lt;/code&gt; , then we print out the message to output.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;loop {
    let msg = socket.read_message().expect(&quot;Error reading message&quot;).to_string();
    if letOk(elems) = parse_ris_live_message(msg.as_str()) {
        for elem in elems {
            if letSome(origins) = elem.origin_asns.as_ref() {
                if origins.contains(&amp;#x26;32934) &amp;#x26;&amp;#x26;
                    ( elem.prefix.to_string() ==  &quot;129.134.30.0/23&quot;.to_string() ||
                        elem.prefix.to_string() ==  &quot;185.89.218.0/23&quot;.to_string() )
                {
                    println!(&quot;{}&quot;, elem);
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full example code can be found here:&lt;/p&gt;
&lt;p&gt;%[&lt;a href=&quot;https://gist.github.com/digizeph/fcac3027555c0b744ea0b3a11197b694#file-bgpkit-parser-kafka-bmp-example-rs&quot;&gt;https://gist.github.com/digizeph/fcac3027555c0b744ea0b3a11197b694#file-bgpkit-parser-kafka-bmp-example-rs&lt;/a&gt;]&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Building More with BGPKIT Tools&lt;/h2&gt;
&lt;p&gt;As introduced in our &lt;a href=&quot;https://bgpkit.com/blog/real-time-bgp-data-processing-1-bmp-and-openbmp&quot;&gt;previous blog post&lt;/a&gt;, we added support of real-time BMP stream to BGPKIT Parser as well. Combining with RIPE RIS Live, and RouteViews BMP stream, we can build a powerful real-time BGP monitoring service directly within BGPKIT Parser. We also offer indexing and processing of historical BGP data as well with &lt;a href=&quot;https://bgpkit.com/broker&quot;&gt;BGPKIT Broker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our goal at BGPKIT is to design, develop, and deploy the most developer-friendly BGP data processing toolkit. To learn more about our offerings, please check out our website and official &lt;a href=&quot;https://twitter.com/bgpkit&quot;&gt;Twitter account&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Real-time BMP with BGPKIT Parser</title><link>https://bgpkit.com/blog/real-time-bgp-data-processing-1-bmp-and-openbmp/</link><guid isPermaLink="true">https://bgpkit.com/blog/real-time-bgp-data-processing-1-bmp-and-openbmp/</guid><description>Real-time BGP data processing is very critical on building monitoring services that can detect BGP issues quickly with minimum delay and react to anomalies quic...</description><pubDate>Wed, 10 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Real-time BGP data processing is very critical on building monitoring services that can detect BGP issues quickly with minimum delay and react to anomalies quickly and mitigate potential issues.&lt;/p&gt;
&lt;p&gt;We are creating a new series of posts describing how we design our software to work with real-time BGP data streams. As an opening, we will describe how we handle data streams with BMP protocol and OpenBMP messages.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;BMP&lt;/h1&gt;
&lt;p&gt;The BGP Monitoring Protocol (BMP) is a protocol that allows monitoring of BGP devices.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7854&quot;&gt;RFC7854&lt;/a&gt; describes the purpose of BMP as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Many researchers and network operators wish to have access to the contents of routers’ BGP Routing Information Bases (RIBs) as well as a view of protocol updates the router is receiving. This monitoring task cannot be realized by standard protocol mechanisms. Prior to the introduction of BMP, this data could only be obtained through screen scraping.&lt;/p&gt;
&lt;p&gt;BMP provides access to the Adj-RIB-In of a peer on an ongoing basis and a periodic dump of certain statistics the monitoring station can use for further analysis. From a high level, BMP can be thought of as the result of multiplexing together the messages received on the various monitored BGP sessions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are multiple types of BMP messages, each serving different purposes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Peer up and down notification: notification about the status of peering sessions to a monitored router;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initiation message: inform the monitoring station of the routers vendor, software version, and so on;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Termination message: provides information on why a monitored router is terminating a session;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Route monitoring&lt;/strong&gt;:initial synchronization of the routing table;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Route mirroring&lt;/strong&gt;: verbatim duplication of messages as received.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For real-time BGP data processing, we are specifically interested in the route monitoring and route-mirroring messages, as we provide the routing information encoded as actual BGP messages.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;OpenBMP&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.openbmp.org/&quot;&gt;OpenBMP&lt;/a&gt; is a software implementation of the BMP protocol. It is an open-source project created by Cisco and currently maintained by nice folks from &lt;a href=&quot;https://www.caida.org/&quot;&gt;CAIDA/UCSD&lt;/a&gt; and &lt;a href=&quot;https://routeviews.org/&quot;&gt;RouteViews&lt;/a&gt;. It is implemented in C++, can be used with any compliant BMP sender (e.g., router).&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/openbmp-architecture.B0H8ukJV.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Architecture graph of OpenBMP&lt;/p&gt;
&lt;p&gt;OpenBMP provides multiple formats for outputting the BMP messages collected from the connected routers, one of which is the &lt;code&gt;raw_bmp&lt;/code&gt; format, which is a thin wrapper of the raw BMP messages. The &lt;code&gt;raw_bmp&lt;/code&gt; format provides the best performance and allows use to handle the BMP messages directly without having to write a different parser for the plaintext messages.&lt;/p&gt;
&lt;p&gt;RouteViews currently provides a OpenBMP Kafka stream that streams BMP messages from their collectors.&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;BGPKIT Parser with BMP/OpenBMP Support&lt;/h1&gt;
&lt;p&gt;We develop BGPKIT Parser to provide a one-stop solution for handling all parsing tasks regarding BGP data. Supporting real-time data like BMP is a very important milestone for us.&lt;/p&gt;
&lt;p&gt;We have recently developed the full support for BMP messages, and partial support for OpenBMP messages (for &lt;code&gt;raw_bmp&lt;/code&gt; type only). This enables us to start working with real-time BMP streams like RouteViews’ Kafka stream.&lt;/p&gt;
&lt;p&gt;Below is an example code that takes RouteViews’ Kafka OpenBMP stream and parse the messages into internal data structures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let mut reader = Cursor::new(Vec::from(kafka_payload));
let header = parse_openbmp_header(&amp;#x26;mut reader).unwrap();
if let Ok(msg) = parse_bmp_msg(&amp;#x26;mut reader) {
    info!(&quot;Parsing OK: {:?}&quot;, msg.common_header.msg_type);
    match msg.message_body {
        MessageBody::RouteMonitoring(m) =&gt; {
            dbg!(m.bgp_update);
        }
        _ =&gt; {}
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a break down of what it does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;it first creates a bytes reader from the raw Kafka message payload;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;then parse OpenBMP message header, which contains some basic information about the BMP session;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;then it calls the &lt;code&gt;parse_bmp_msg&lt;/code&gt; function to parse the embedded raw BMP messages and print out the BGP update messages if the parsing is successful.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a full code example:&lt;/p&gt;
&lt;p&gt;%[&lt;a href=&quot;https://gist.github.com/digizeph/fcac3027555c0b744ea0b3a11197b694#file-bgpkit-parser-kafka-bmp-example-rs&quot;&gt;https://gist.github.com/digizeph/fcac3027555c0b744ea0b3a11197b694#file-bgpkit-parser-kafka-bmp-example-rs&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;We have published the SDK on &lt;a href=&quot;https://crates.io/crates/bgpkit-parser&quot;&gt;crates.io&lt;/a&gt; and &lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser&quot;&gt;GitHub&lt;/a&gt;. Feel free the check out the example code at &lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser/blob/main/examples/real-time-routeviews-kafka-openbmp.rs&quot;&gt;examples/routeviews-kafka.rs&lt;/a&gt; if you are interested.&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Introducing BGPKIT Parser</title><link>https://bgpkit.com/blog/introducing-bgpkit-parser/</link><guid isPermaLink="true">https://bgpkit.com/blog/introducing-bgpkit-parser/</guid><description>BGPKIT Parser is an open-source Rust-based MRT/BGP data parser that takes a MRT formatted binary file and turns it into BGP messages. It is one of the most impo...</description><pubDate>Mon, 01 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;img src=&quot;https://bgpkit.com/_astro/parser-architecture.CTkI0CeB.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;BGPKIT Parser is an open-source Rust-based MRT/BGP data parser that takes a &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6396&quot;&gt;MRT&lt;/a&gt; formatted binary file and turns it into BGP messages. It is one of the most important building block software that enables BGP data processing and analysis tasks.&lt;/p&gt;
&lt;h2&gt;Design and Features&lt;/h2&gt;
&lt;p&gt;As mentioned in our previous post &lt;a href=&quot;https://medium.com/bgpkit/introducing-bgpkit-broker-b734dac4661e&quot;&gt;introducing BGPKIT Broker&lt;/a&gt;, the most used BGP data collection projects, RouteViews and RIPE RIS, both publish their collected BGP data in MRT format on their data platform. The BGPKIT Parser is designed to handle parsing tasks for these data sources.&lt;/p&gt;
&lt;p&gt;We design our parser to strictly follow the industry standard, e.g. &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc4271&quot;&gt;RFC4271&lt;/a&gt; and &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6396&quot;&gt;RFC6396&lt;/a&gt;. BGPKIT Parser is also designed with the following goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;performant&lt;/strong&gt;: comparable to C-based implementations like &lt;code&gt;bgpdump&lt;/code&gt; or &lt;code&gt;bgpreader&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;actively maintained&lt;/strong&gt;: we consistently introduce feature updates and bug fixes, and support most of the relevant BGP RFCs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ergonomic API&lt;/strong&gt;: a three-line for loop can already get you started.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;battery-included&lt;/strong&gt;: ready to handle remote or local, &lt;code&gt;bzip2&lt;/code&gt; or &lt;code&gt;gz&lt;/code&gt; data files out of the box.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;open-source&lt;/strong&gt;: we want people to use our parser freely and we can continue develop and improve it based on community feedbacks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To demonstrate how easy it is to get started using BGPKIT Parser, check out the example below where we print out all BGP messages from a remote MRT file on RouteViews:&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/parser-remote-example.BSEtLIra.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Example of reading a remote MRT file from RouteViews and print out BGP messages. Code available at &lt;a href=&quot;https://gist.github.com/digizeph/9977371653f39a459ff3ae507dc3636c&quot;&gt;https://gist.github.com/digizeph/9977371653f39a459ff3ae507dc3636c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There are a number of things happens when we call &lt;code&gt;for elem in BgpkitParser::new(url)&lt;/code&gt; :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;it creates a new BgpkitParser struct instance with the provided URL to the data file;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it tries to retrieve the content of the remote file and download the raw compressed bytes into memory;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it determines the compression type by file suffix and calls corresponding decompression library to create a buffered reader;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it then creates an iterator (used by the for loop) that continuous return new parsed items until it reaches the end of the data stream.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We determined that it is worth the extra binary file size to bring in the network and compression libraries into the project so that the library users will never have to worry about handling data downloading and decompression by themselves again.&lt;/p&gt;
&lt;h2&gt;The Future&lt;/h2&gt;
&lt;p&gt;The future of the BGPKIT Parser lies on continuous performance and stability improvements, as well as some exciting features that we are currently planning. Some of the coming features include&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;adding capability of handling &lt;strong&gt;real-time data streams&lt;/strong&gt; coming from RIPE RIS Live and RotueViews’s Kafka BMP stream;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;supporting &lt;strong&gt;data serialization&lt;/strong&gt; back to MRT files (reverse-parsing), which allows users to produce customized MRT files after data processing;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;adding &lt;strong&gt;WASM support&lt;/strong&gt; to allow BGP data parsing directly on the web with JavaScript.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because we are building our software in Rust, we can effortlessly tapping into Rust’s great software ecosystem and continue introducing new features and improvements. The future of BGPKIT Parser is exciting and we can’t wait to bring more features for you to try out!&lt;/p&gt;
&lt;p&gt;For more details about the BGPKIT Parser, check out our GitHub repo and our website. Feedback is highly appreciated!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-parser&quot;&gt;https://github.com/bgpkit/bgpkit-parser&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>Introducing BGPKIT Broker</title><link>https://bgpkit.com/blog/introducing-bgpkit-broker/</link><guid isPermaLink="true">https://bgpkit.com/blog/introducing-bgpkit-broker/</guid><description>BGPKIT Broker is a data API service that focuses on building a BGP data file index to enable searching for public/private BGP data files with custom filters. It...</description><pubDate>Sun, 31 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BGPKIT Broker is a data API service that focuses on building a BGP data file index to enable searching for public/private BGP data files with custom filters. It is one of the building block components designed by BGPKIT to facilitate BGP data processing with ease.&lt;/p&gt;
&lt;h2&gt;The first step to investigate a BGP event.&lt;/h2&gt;
&lt;p&gt;Imagine this scenario: a malicious player just attempted to hijack a IP prefix using BGP announcements, and you are interested in learning what exactly happened during that half-hour down time of the victim network, how would you start investigating?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Collecting evidences&lt;/strong&gt;. Luckily for us, the actors on the Internet, good and bad, always leave traces behind if they use BGP as their method. There are a number of &lt;strong&gt;reputable public BGP route collectors&lt;/strong&gt; operating for years collecting all BGP messages received from their connected router peers and dump them into regular dump files. The most used projects are &lt;a href=&quot;https://routeviews.org/&quot;&gt;RouteViews&lt;/a&gt; and &lt;a href=&quot;https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris/ris-raw-data&quot;&gt;RIPE RIS Data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Files are all over the places.&lt;/strong&gt; There are more than 60 different data collectors from the two projects alone, and each publishing their data under separate sites. The two projects also have different data file structure and compression algorithms for their data dump files. It is not hard to go find one data file during the interested event time from one collector, but it will be a real hassle to gather URLs toward &lt;strong&gt;all data files&lt;/strong&gt; that include information within a specified time range.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/collector-mrt-files.NhsyDmjV.png&quot; alt=&quot;Collector’s data is published as compressed MRT files at regular frequency.&quot;&gt;
&lt;p&gt;Collector’s data is published as compressed MRT files at regular frequency.&lt;/p&gt;
&lt;h2&gt;BGPKIT Broker — A BGP Data File Index API Service&lt;/h2&gt;
&lt;p&gt;We designed the BGPKIT Broker to resolve one and only one problem: quickly collect links to the BGP data files that matches a filtering criteria.&lt;/p&gt;
&lt;p&gt;You can filter BGP data using multiple criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;start_ts&lt;/code&gt; : UNIX timestamp that all files must be dumped after&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;end_ts&lt;/code&gt; : UNIX timestamp that all files must be dumped before&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;data_type&lt;/code&gt; : the type of the data file, can be &lt;code&gt;update&lt;/code&gt; or &lt;code&gt;rib&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;collector&lt;/code&gt;: the collector ID that the files are generated from&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;project&lt;/code&gt;: the data collection project, can be &lt;code&gt;route-views&lt;/code&gt; or &lt;code&gt;riperis&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;page&lt;/code&gt; and &lt;code&gt;page_size&lt;/code&gt;: the pagination control for collecting a large number of files&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example REST API call: &lt;a href=&quot;https://api.broker.bgpkit.com/v1/search?data_type=update&amp;#x26;start_ts=1633046400&amp;#x26;end_ts=1633132800&amp;#x26;collector=rrc00&amp;#x26;project=riperis&amp;#x26;page%20=2&amp;#x26;page_size=3&quot;&gt;https://api.broker.bgpkit.com/v1/search?data_type=update&amp;#x26;start_ts=1633046400&amp;#x26;end_ts=1633132800&amp;#x26;collector=rrc00&amp;#x26;project=riperis&amp;#x26;page%20=2&amp;#x26;page_size=3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It asks for all data &lt;strong&gt;updates&lt;/strong&gt; files dumped between &lt;strong&gt;1633046400&lt;/strong&gt; and &lt;strong&gt;1633132800&lt;/strong&gt;, from &lt;strong&gt;RIPE RIS&lt;/strong&gt;’s collector &lt;strong&gt;rrc00&lt;/strong&gt;. It also requested for the &lt;strong&gt;second page&lt;/strong&gt; of the results and &lt;strong&gt;each page contains 3 items&lt;/strong&gt;.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/broker-api.D4KMyvpf.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;&lt;strong&gt;DEPRECATED&lt;/strong&gt;: BGPKIT Broker API service is freely available to use, hosted at &lt;a href=&quot;https://api.broker.bgpkit.com/v1/&quot;&gt;https://api.broker.bgpkit.com/v1/&lt;/a&gt;. The documentation is available at &lt;a href=&quot;https://docs.broker.bgpkit.com/&quot;&gt;https://docs.broker.bgpkit.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BGPKIT Broker API and SDK has been upgraded to V2 now. The V1 examples are left up for legacy services that depends on it. Please check out the current API documentation at for more:&lt;/strong&gt;&lt;a href=&quot;https://api.broker.bgpkit.com/v2/&quot;&gt;&lt;strong&gt;https://api.broker.bgpkit.com/v2/&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;BGPKIT Broker Rust API&lt;/h2&gt;
&lt;p&gt;The BGPKIT Broker API service is built entirely using Rust, and of course we also developed native Rust API to access the broker data with ease using Rust.&lt;/p&gt;
&lt;img src=&quot;https://bgpkit.com/_astro/broker-rust-api.DMXU0OSc.png&quot; alt=&quot;&quot;&gt;
&lt;p&gt;Example BGPKIT Broker Rust API call&lt;/p&gt;
&lt;p&gt;The Rust API is open source under MIT license, and with the free API, you can already build your own workflow today!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bgpkit/bgpkit-broker&quot;&gt;https://github.com/bgpkit/bgpkit-broker&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Easy On-premise Deployment&lt;/h2&gt;
&lt;p&gt;For API service like this, especially that it also collects and indexes data of over 10 years span, one might imagine the deployment could be complex and slow.&lt;/p&gt;
&lt;p&gt;For BGPKIT Broker, we spent extra efforts to make the API deployment process as quickly as possible, and also efficient on resource consumption. For references, we bootstrapped the entirety of the database in &lt;strong&gt;under 5 minutes&lt;/strong&gt;, and cost &lt;strong&gt;less than 1 GB storage&lt;/strong&gt; to store the information in PostgreSQL database. The whole database and API run fluently with &lt;strong&gt;less than 500MB of RAM&lt;/strong&gt; usage. This allows use to deployment extra instances when under heavy load without costing a fortune.&lt;/p&gt;
&lt;p&gt;Users who need dedicated resource allocation for query performance can contact us for private API hosting that are not shared by others. Enterprise option is also available for on-premise deployment with customization consultation available. If you are interested in testing it out, feel free to shoot us an email at &lt;a href=&quot;mailto:contact@bgpkit.com&quot;&gt;contact@bgpkit.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For more information, checkout our website!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bgpkit.com/broker&quot;&gt;https://bgpkit.com/broker&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item><item><title>BGPKIT Journey Started</title><link>https://bgpkit.com/blog/bgpkit-journey-started/</link><guid isPermaLink="true">https://bgpkit.com/blog/bgpkit-journey-started/</guid><description>BGPKIT is a small-team start-up that aims to provide comprehensive tool suite to facilitate companies building on-premise BGP data monitoring services. We start...</description><pubDate>Tue, 26 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://bgpkit.com/&quot;&gt;BGPKIT&lt;/a&gt; is a small-team start-up that aims to provide comprehensive tool suite to facilitate companies building on-premise BGP data monitoring services. We started our journey of building the best BGP data toolkit for developers in October, 2021. Here is a brief glance on what we are working on and what values we strive to provide.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bgpkit.com&quot;&gt;&lt;img src=&quot;https://bgpkit.com/_astro/bgpkit-logo.Dt47iy_S.png&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;BGP&lt;/h1&gt;
&lt;p&gt;Border Gateway Protocol (BGP) is the de facto inter-domain routing protocols being used by every major companies on the Internet. &lt;strong&gt;The main purpose of BGP is to allow companies exchange IP prefix reachability information.&lt;/strong&gt; In other words, companies tell other companies what IP blocks they have, and how to reach these IP blocks. This the key functionality that enables the Internet.&lt;/p&gt;
&lt;p&gt;Because the purpose of BGP is to exchange information and allow everyone to know how to reach certain IP blocks, the BGP messages must be propagated globally and publicly. There are a number of data sources available out there that provides BGP data archives (e.g. &lt;a href=&quot;https://www.routeviews.org/routeviews/&quot;&gt;RouteViews&lt;/a&gt; and &lt;a href=&quot;https://www.ripe.net/analyse/internet-measurements/routing-information-service-ris/ris-raw-data&quot;&gt;RIPE RIS&lt;/a&gt;), or real-time data like BGP &lt;a href=&quot;https://lg.he.net/&quot;&gt;looking glass&lt;/a&gt;es or &lt;a href=&quot;https://ris-live.ripe.net/&quot;&gt;live BGP data stream&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The aforementioned data sources contains a wealth of information if you know what to look for. For example, by looking at BGP information, researchers can &lt;a href=&quot;https://www.caida.org/catalog/datasets/as-relationships/&quot;&gt;infer companies relationships&lt;/a&gt;, monitor &lt;a href=&quot;https://www.bgpmon.net/&quot;&gt;security&lt;/a&gt; &lt;a href=&quot;https://radar.qrator.net/&quot;&gt;incidences&lt;/a&gt;. Having handy toolkit in hand enables people to build successful businesses around BGP data.&lt;/p&gt;
&lt;h1&gt;BGPKIT&lt;/h1&gt;
&lt;p&gt;At BGPKIT, we build software tools to process BGP data and reveal insights from BGP messages. We aims to provide the best developer experience, and enable customers to build their own BGP data processing pipeline and monitoring services on-premise.&lt;/p&gt;
&lt;h2&gt;Complete Tool Suite&lt;/h2&gt;
&lt;p&gt;Our goal is to build complete tool suite for BGP data processing: data collection, parsing, analysis, programmable and visual interface, and data warehousing. Everything you need to handle BGP data.&lt;/p&gt;
&lt;h2&gt;Rust Implementation&lt;/h2&gt;
&lt;p&gt;To achieve the best performance and security, we choose to focus on building our tools using the &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust programming language&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We believe that Rust’s ecosystem is now mature enough that building modern features like data streaming, async data workflow, parallel processing, web API, or even porting the entire codebase onto WASM and running it on browsers is a archivable task with reasonable efforts.&lt;/p&gt;
&lt;h2&gt;Powerful Extensibility&lt;/h2&gt;
&lt;p&gt;At BGPKIT, we design our libraries to provide powerful API and assist customers to further customize workflow to meet individual needs. We strive to provide the most ergonomic interfaces that allow library consumers to easily integrate our library into theirs.&lt;/p&gt;
&lt;h2&gt;Embrace Open-Source&lt;/h2&gt;
&lt;p&gt;We also believe that good tools empowers people, so we open-sourced our building block libraries to the public with very permissive license so that any interested parties can explore and build their own ideas free of charge and limitation.&lt;/p&gt;
&lt;p&gt;We are excited to start this journey of building, and we hope to have the chance to work with more people and building more dreams together! Follow us here, on &lt;a href=&quot;https://twitter.com/bgpkit&quot;&gt;Twitter&lt;/a&gt;, or visit our website to learn more!&lt;/p&gt;</content:encoded><dc:creator>Mingwei Zhang</dc:creator></item></channel></rss>