Utilities
Prefix trees
Everything that measures a radix.Radix tree lives in quickrib.radix_utils.
quickrib.radix_utils
Everything that reads a radix.Radix prefix tree.
The RIB is a radix tree per vantage point (see
:data:~quickrib.rib_table.RIBData), and several observers measure the address
space one holds. Those measurements used to be spread across the observers that
happened to need them first; they live here so there is one implementation of
each and one place to test them (tests/test_radix.py).
Two questions come up, and they are not the same one:
- How much space does this one node contribute? :func:
radix_prefix_sizesubtracts the node's direct children, so a prefix and the more specifics announced inside it do not both claim the same addresses. This is what AS hegemony weights a path by. - How much space does the whole tree cover? :func:
radix_size(in addresses) and :func:radix_block_count(in /24s or /48s) sum only the least specific nodes, since anything nested inside one of them is already counted. This is the union, and it is what IODA's visible-/24s signal reports.
Every function reads the address family off each node, so a tree holding both IPv4 and IPv6 prefixes is measured correctly.
PRECOMPUTED_BLOCK_SIZES_IPV4 = {plen: (2 ** (32 - plen)) for plen in (range(33))}
module-attribute
Number of addresses in an IPv4 prefix, by prefix length.
PRECOMPUTED_BLOCK_SIZES_IPV6 = {plen: (2 ** (128 - plen)) for plen in (range(129))}
module-attribute
Number of addresses in an IPv6 prefix, by prefix length.
PRECOMPUTED_BLOCK_COUNTS_IPV4 = {plen: (1 << 24 - plen) for plen in (range(25))}
module-attribute
Number of /24s in an IPv4 prefix, by prefix length. Empty above /24.
PRECOMPUTED_BLOCK_COUNTS_IPV6 = {plen: (1 << 48 - plen) for plen in (range(49))}
module-attribute
Number of /48s in an IPv6 prefix, by prefix length. Empty above /48.
tree_size(rtree)
Number of prefixes in rtree.
py-radix defines no __len__. nodes() materialises a list, but it
does so in C and is several times faster than iterating the tree from
Python (16ms against 60ms for 160k prefixes).
Source code in src/quickrib/radix_utils.py
48 49 50 51 52 53 54 55 | |
block_size(prefix)
Nominal number of addresses in prefix, ignoring any tree it sits in.
Cheap enough for a hot path: one string split and one dict lookup.
Source code in src/quickrib/radix_utils.py
58 59 60 61 62 63 64 65 66 | |
direct_children(rtree, rnode)
Yield the nodes whose immediate parent in rtree is rnode.
search_covered returns the whole subtree, including rnode itself and
every grandchild; the parent test keeps only the first generation. A node is
never its own parent, so rnode drops out without a special case.
Source code in src/quickrib/radix_utils.py
69 70 71 72 73 74 75 76 77 78 79 | |
radix_prefix_size(rtree, rnode)
Addresses rnode contributes, after deaggregation.
Its own block minus its direct children's: the space they cover is theirs to account for, and counting it twice is what makes a naive sum over a peer's table exceed the address space it actually routes. Grandchildren are already subtracted from their own parent, so they must not be subtracted again here.
Source code in src/quickrib/radix_utils.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
radix_size(rtree)
Number of addresses the union of rtree's prefixes covers.
Source code in src/quickrib/radix_utils.py
120 121 122 | |
radix_block_count(rtree)
Number of /24s (or /48s for IPv6) the union of rtree covers.
:func:radix_size in the unit operators route in. A prefix longer than the
block contributes nothing, so a lone /25 counts as zero /24s.
Source code in src/quickrib/radix_utils.py
125 126 127 128 129 130 131 | |
Elements
What an observer is handed, and what a radix node stores.
quickrib.elements
What QuickRIB puts in front of its observers.
pybgpflux yields a :class:~pybgpflux.BGPElement whose fields is a
total=False TypedDict shared by every kind of MRT record it can parse, so
as-path is typed as the wire string and everything is optional.
QuickRIB narrows both of those before an element reaches an observer:
- :func:
~quickrib.quickrib.process_pathrewritesas-pathin place from the wire string to a list of ASNs, and drops the element entirely if the path is malformed or contains an AS set. :class:ParsedElementis that same element with the field typed truthfully. - Only
R,AandWelements exist.pybgpfluxnever yields BGP state-change records: itsbgpdumpparser filters them and its elementtypeisLiteral["R", "A", "W"]. A prefix is therefore always present on the elements QuickRIB handles, even thoughElementFieldsallows its absence. Theold-state/new-statekeys there are vestigial, inherited frompybgpstream, which does emit state changes.
:class:RIBNodeData is the other half: what a radix node's .data holds, and
what observers are handed as data / old_data.
ParsedFields = TypedDict('ParsedFields', {'prefix': str, 'as-path': list[str], 'communities': list[str], 'next-hop': NotRequired[str]})
module-attribute
fields of a route (R or A), after QuickRIB has parsed the AS path.
Everything a route carries is required: process_path drops any element
whose path is missing or malformed, so by the time an observer sees one, the
prefix, the path and the community list are all there. Only next-hop, which
QuickRIB never reads, may be absent.
AnyElement = ParsedElement | WithdrawalElement
module-attribute
Either kind. Use it for helpers that only touch what both carry: the peer,
the collector, the timestamp, and fields["prefix"].
RIBNodeData = TypedDict('RIBNodeData', {'as-path': list[str], 'communities': list[str], 'time': float, 'as-path-history': NotRequired['deque[list[str]]']})
module-attribute
What a radix node's .data holds, and what observers get as data.
RIBTable writes all three on every insert, so all three are required.
as-path-history is the exception: it only exists when the RIB is a
:class:~quickrib.observers.update_tagger.RIBTablePathHistory.
WithdrawalFields
Bases: TypedDict
fields of a withdrawal, which names a prefix and nothing else.
Source code in src/quickrib/elements.py
46 47 48 49 | |
ParsedElement
Bases: NamedTuple
A route whose as-path has been parsed into a list of ASNs.
Structurally identical to :class:~pybgpflux.BGPElement. QuickRIB does not
build a new object per message; it just stops lying about the field types
once the path has been rewritten.
Source code in src/quickrib/elements.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
WithdrawalElement
Bases: NamedTuple
A withdrawal, whose fields hold only the prefix being withdrawn.
Source code in src/quickrib/elements.py
68 69 70 71 72 73 74 75 76 | |