Skip to content

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

  • community holds 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

  • seq is 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.
  • owner in nodes is empty unless resolve_owners=True, which queries the RIPEstat as-names call.
  • 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.