BGPlay
BGPlayObserver produces the payload of the RIPEstat
bgplay data call, the
feed behind BGPlay. For one resource it
records the routing state at start_time and every update seen afterwards, so
a client can animate how the paths towards that resource evolve.
Usage
"""Reproduce the RIPEstat bgplay payload for one prefix.
The payload is the routing state at `start_time` plus every update afterwards,
which is what drives the BGPlay animation. `diff_bgplay` compares it against the
live API response.
"""
import datetime
import json
import urllib.parse
import urllib.request
from pathlib import Path
from quickrib import QuickRIB, QuickRIBConfig
from quickrib.observers import BGPlayObserver, diff_bgplay, format_diff
RESOURCE = "140.78.0.0/16"
config = QuickRIBConfig(
start_time=datetime.datetime(2025, 3, 3, 0, 0),
end_time=datetime.datetime(2025, 3, 3, 2, 0),
collectors=["rrc04"],
dump_res=datetime.timedelta(minutes=5),
cache_dir=Path("cache"),
parser="bgpkit",
# The RIPE APIs report partial-feed peers too, so keep them.
fullfeed_only=False,
)
quickrib = QuickRIB.from_config(config)
observer = BGPlayObserver(RESOURCE, config.start_time, config.end_time)
quickrib.rib.attach_observer(observer)
quickrib.run()
ours = observer.to_dict()
print(f"{len(ours['initial_state'])} initial paths, {len(ours['events'])} events, "
f"{len(ours['sources'])} peers")
# Compare against the live API.
query = urllib.parse.urlencode({
"resource": RESOURCE,
"starttime": config.start_time.strftime("%Y-%m-%dT%H:%M:%S"),
"endtime": config.end_time.strftime("%Y-%m-%dT%H:%M:%S"),
"rrcs": "4",
})
url = f"https://stat.ripe.net/data/bgplay/data.json?{query}"
with urllib.request.urlopen(url, timeout=300) as response:
theirs = json.load(response)["data"]
print("\n".join(format_diff(diff_bgplay(ours, theirs))))
A resource is a prefix, an IP address, an AS number (AS1205 or 1205), or a
comma-separated list of those, following the API's grammar. to_dict() returns
the initial_state, events, nodes, sources and targets sections of the
API response, in the API's own order, so the result can be handed to a BGPlay
client as is. The end-to-end test asserts the payload against the live API.
Rules the API does not document
communityholds standard communities only; extended and large communities, which MRT parsers do report, are dropped.- An AS resource matches the origin of each path, not the set of prefixes the AS originates, and carries no withdrawals, since a withdrawal has no path to attribute. Query the prefix to see them.
- A prefix resource matches exactly; its more specifics are not included.
- Prefixes sort numerically, IPv4 before IPv6.
- Repeated identical announcements are reported each time.
Differences from the API
seqis a 0-based index rather than RIPE's internal counter.- Events within the same second come out in archive order rather than in RIS ingestion order, which a replay cannot recover. The set of events is identical.
ownerinnodesis empty unlessresolve_owners=True, which queries the RIPEstatas-namescall.- A bare IP resource expands to every covering prefix observed, where the API resolves it to one.
diff_bgplay compares two payloads section by section as multisets and reports
what is only on each side, what differs, and a Jaccard similarity;
is_equivalent and format_diff reduce and render that report.