Skip to content

Update Classification

UpdateTagger labels every BGP update with what it changed in the peer's table, following the BLT taxonomy of Kitabatake, Fontugne and Esaki, BLT: A Taxonomy and Classification Tool for Mining BGP Update Messages (IEEE INFOCOM Workshops, Global Internet Symposium, 2018). The premise is that an update message is only meaningful relative to the table it modifies: the same announcement is a new route, a path change or a duplicate depending on what the peer held before, which is exactly what the reconstruction provides.

Tags are not exclusive. One announcement can change the path, the prepending and the communities at once.

Tag Meaning
UPDATE_MESSAGE Every update. The denominator for the others.
NEW_PREFIX The peer did not have this prefix.
REMOVE_PREFIX A withdrawal of a prefix the peer had.
ORIGIN_CHANGE The origin AS changed.
TRANSIT_CHANGE The path changed, the origin did not.
PATH_SWITCHING The path returned to where it was two updates ago.
PREPENDING_ADD / PREPENDING_REMOVE / PREPENDING_CHANGE Repeated ASNs were added, removed or rearranged.
COMMUNITY_CHANGE The community list changed.
DUPLICATE_ANNOUNCE A re-announcement that changed nothing stored.
DUPLICATE_WITHDRAWAL A withdrawal of a prefix the peer did not have.

BLT's other attribute change is not produced: the table stores the AS path and the communities only, so an update that changes another attribute is indistinguishable from a duplicate.

Usage

UpdateTagsCounter accumulates the tags and returns the totals of each window from dump, as {tag: count} with every tag present:

import datetime
import logging
import sys
from pathlib import Path

import requests_cache

from quickrib import QuickRIB, QuickRIBConfig
from quickrib.observers.update_tagger import RIBTablePathHistory, UpdateTagsCounter

FORMAT = "%(asctime)s %(levelname)s %(message)s"
CACHE = Path("cache")
CACHE.mkdir(exist_ok=True)
logging.basicConfig(
    format=FORMAT,
    handlers=[
        logging.FileHandler(CACHE / "example_update_classifier.log"),
        logging.StreamHandler(sys.stdout),
    ],
    # INFO, not DEBUG: the pipeline logs per-peer prefix counts at DEBUG, and
    # counting them walks every tree.
    level=logging.INFO,
    datefmt="%Y-%m-%d %H:%M:%S",
)

# Beside the MRT archives, rather than in whatever directory this was run from.
requests_cache.install_cache(str(CACHE / "http_cache"))

# Define study parameters 
config = QuickRIBConfig(
    start_time=datetime.datetime(2010, 9, 1, 0, 0),
    end_time=datetime.datetime(2010, 9, 1, 1, 59),
    collectors=["route-views.wide", "rrc04"],
    dump_res=datetime.timedelta(minutes=5),
    cache_dir=Path("cache"),
    parser="bgpkit",
    rib_cls=RIBTablePathHistory
)
quickrib = QuickRIB.from_config(config)

# Use a built-in analysis module. `UpdateTagsCounter` accumulates the BLT tags
# and logs the totals once per `dump_res`; `UpdateTagger` is the same
# classification without the counting, and with `log_tags=True` it traces every
# individual message at DEBUG, which suits a filtered stream but not this one.
#
# `rib_cls=RIBTablePathHistory` above is what makes PATH_SWITCHING possible: it
# keeps each prefix's previous AS paths on the node, which route-flap detection
# needs and a plain RIBTable does not carry.
observer = UpdateTagsCounter()

# Bind observers to RIB
quickrib.rib.attach_observer(observer)

quickrib.run()

UpdateTagger performs the classification without counting and returns the flags from each hook, for a subclass to consume.

PATH_SWITCHING needs to know where the path was before the previous update, which a single old_data cannot say. RIBTablePathHistory stores that history; without it the tag never fires.

Building on the tags

Tag counts per window form a time series, and a surge in one class is the anomaly BLT was designed to expose. examples/origin_hijack.py scores the per-window ORIGIN_CHANGE count against its recent past, a minimal hijack detector.