check / check (push) Canceled after 0s
A new routes table holds one row per prefix and origin AS: the peer set as the bitset's bytes, and the two kept routes, each with its peer's number, AS path and time in seconds. A new route_peers table holds each peer's number and address. WriteRouteChanges writes one TakeChanges result in a transaction. ReadRouteTable reads both tables into a new routing table, through the new Table.Load, which records no change. Nothing uses them yet. Model: opus-5-5
637 lines
18 KiB
Go
637 lines
18 KiB
Go
// Package routetable holds the whole routing table in memory. For each prefix
|
|
// and origin AS, it keeps the peers whose current route to the prefix has that
|
|
// origin AS. It also keeps the two newest of those routes in full. It applies
|
|
// announcements, withdrawals and peers' sessions going down. It answers the
|
|
// route reads that the pages and the API need.
|
|
package routetable
|
|
|
|
import (
|
|
"cmp"
|
|
"net/netip"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/bits-and-blooms/bitset"
|
|
"github.com/google/btree"
|
|
)
|
|
|
|
const (
|
|
// keptRoutesPerOrigin is how many routes of each prefix and origin AS are
|
|
// kept in full: the newest ones. Every other peer that carries the prefix
|
|
// with that origin AS keeps only its bit in the peer set.
|
|
keptRoutesPerOrigin = 2
|
|
|
|
// keptTimesDegree is the degree of the B-tree that holds the times of the
|
|
// kept routes in order: each of its nodes holds up to 63 times.
|
|
keptTimesDegree = 32
|
|
|
|
ipv4Bits = 32
|
|
ipv6Bits = 128
|
|
|
|
// The address lookup ignores prefixes shorter than these mask lengths. A
|
|
// default route or a route this short only says where a peer sends traffic
|
|
// it has no better route for; it does not say who holds the address.
|
|
minLookupMaskLengthV4 = 8
|
|
minLookupMaskLengthV6 = 16
|
|
|
|
// The IP versions SamplePrefixes takes.
|
|
ipVersion4 = 4
|
|
ipVersion6 = 6
|
|
)
|
|
|
|
// Route is one peer's route to one prefix. The next hop is the peer itself, so
|
|
// it is not stored.
|
|
type Route struct {
|
|
// Peer is the peer's address.
|
|
Peer string
|
|
ASPath []int
|
|
// Time is when the peer announced the route.
|
|
Time time.Time
|
|
}
|
|
|
|
// Origin is one origin AS of a prefix: the peers whose current route to the
|
|
// prefix has that origin AS, and the newest of those routes in full.
|
|
type Origin struct {
|
|
// ASN is the origin AS. 0 means no known origin: the path ends in an AS
|
|
// set of several members.
|
|
ASN int
|
|
// Peers are the peers' addresses, in the order of their numbers: the
|
|
// order they were first seen in.
|
|
Peers []string
|
|
// Routes are the kept routes, newest first: at most two, and none when
|
|
// both were withdrawn while other peers still carry the prefix.
|
|
Routes []Route
|
|
}
|
|
|
|
// Change is the current content of one prefix and origin AS pair that changed:
|
|
// what a database row for the pair holds.
|
|
type Change struct {
|
|
Prefix netip.Prefix
|
|
OriginASN int
|
|
// Removed says that no peer carries the prefix with this origin AS any
|
|
// more. Peers and Routes are then nil.
|
|
Removed bool
|
|
// Peers holds the number of each peer that carries the prefix with this
|
|
// origin AS.
|
|
Peers *bitset.BitSet
|
|
// Routes are the kept routes, newest first.
|
|
Routes []Route
|
|
}
|
|
|
|
// Params configures New.
|
|
type Params struct {
|
|
// PeerNumbers holds the number each peer was given before, by address. A
|
|
// peer not in it gets the next number above all of them the first time it
|
|
// announces a route.
|
|
PeerNumbers map[string]uint
|
|
}
|
|
|
|
// Table is the routing table. Its methods are safe for concurrent use.
|
|
type Table struct {
|
|
mu sync.RWMutex
|
|
|
|
ipv4 family
|
|
ipv6 family
|
|
// byOrigin holds, for each origin AS, the prefixes it originates.
|
|
byOrigin map[int]map[netip.Prefix]*origin
|
|
|
|
// peerNumbers and peerAddresses give each peer's number by its address
|
|
// and its address by its number. nextPeerNumber is the number the next
|
|
// new peer gets.
|
|
peerNumbers map[string]uint
|
|
peerAddresses map[uint]string
|
|
nextPeerNumber uint
|
|
// peerPrefixes counts the prefixes each peer carries, by its number.
|
|
peerPrefixes map[uint]int
|
|
|
|
// newPeers and changed are what TakeChanges hands over next: the peers
|
|
// numbered, and the prefix and origin AS pairs changed, since it last ran.
|
|
newPeers map[string]uint
|
|
changed map[pair]struct{}
|
|
|
|
// keptTimes counts the kept routes at each time, in time order, so its
|
|
// first and last items are the oldest and newest kept routes.
|
|
keptTimes *btree.BTreeG[keptTime]
|
|
// originsWithoutKeptRoute counts the origins that peers carry but that
|
|
// keep no route.
|
|
originsWithoutKeptRoute int
|
|
}
|
|
|
|
// New returns an empty table that numbers new peers on from
|
|
// params.PeerNumbers.
|
|
func New(params Params) *Table {
|
|
t := &Table{
|
|
ipv4: newFamily(ipv4Bits, minLookupMaskLengthV4),
|
|
ipv6: newFamily(ipv6Bits, minLookupMaskLengthV6),
|
|
byOrigin: make(map[int]map[netip.Prefix]*origin),
|
|
peerNumbers: make(map[string]uint, len(params.PeerNumbers)),
|
|
peerAddresses: make(map[uint]string, len(params.PeerNumbers)),
|
|
peerPrefixes: make(map[uint]int),
|
|
newPeers: make(map[string]uint),
|
|
changed: make(map[pair]struct{}),
|
|
keptTimes: btree.NewG(keptTimesDegree, func(a, b keptTime) bool {
|
|
return a.time.Before(b.time)
|
|
}),
|
|
}
|
|
|
|
for address, number := range params.PeerNumbers {
|
|
t.peerNumbers[address] = number
|
|
t.peerAddresses[number] = address
|
|
t.nextPeerNumber = max(t.nextPeerNumber, number+1)
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
// Announce applies an announcement: the peer's current route to prefix is now
|
|
// r, with origin AS originASN. A peer that carried the prefix with another
|
|
// origin AS leaves that origin's peer set, and its route there is dropped. r
|
|
// is kept in full when it is one of the two newest routes of its prefix and
|
|
// origin AS; otherwise only the peer's bit is.
|
|
//
|
|
// An announcement whose path is empty or holds AS 0 is a withdrawal, and the
|
|
// caller passes it to Withdraw instead.
|
|
func (t *Table) Announce(prefix netip.Prefix, originASN int, r Route) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
if !prefix.IsValid() {
|
|
return
|
|
}
|
|
|
|
prefix = prefix.Masked()
|
|
peer := t.numberPeer(r.Peer)
|
|
|
|
current := originWithPeer(t.origins(prefix), peer)
|
|
if current != nil && current.asn != originASN {
|
|
t.leave(prefix, current, peer)
|
|
}
|
|
|
|
t.join(prefix, t.findOrAddOrigin(prefix, originASN), route{
|
|
peer: peer,
|
|
path: slices.Clone(r.ASPath),
|
|
time: r.Time,
|
|
})
|
|
}
|
|
|
|
// Withdraw applies a withdrawal: the peer no longer carries prefix. It leaves
|
|
// the prefix's peer set, and its kept route, if any, is dropped.
|
|
func (t *Table) Withdraw(prefix netip.Prefix, peer string) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
number, known := t.peerNumbers[peer]
|
|
if !known || !prefix.IsValid() {
|
|
return
|
|
}
|
|
|
|
prefix = prefix.Masked()
|
|
|
|
o := originWithPeer(t.origins(prefix), number)
|
|
if o != nil {
|
|
t.leave(prefix, o, number)
|
|
}
|
|
}
|
|
|
|
// PeerDown applies a peer's session going down: the peer leaves every peer
|
|
// set, and every route of it is dropped. When the peer carries any prefix, it
|
|
// visits every prefix in the table.
|
|
func (t *Table) PeerDown(peer string) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
number, known := t.peerNumbers[peer]
|
|
if !known || t.peerPrefixes[number] == 0 {
|
|
return
|
|
}
|
|
|
|
for _, f := range []*family{&t.ipv4, &t.ipv6} {
|
|
for _, prefixes := range f.byLength {
|
|
for prefix, origins := range prefixes {
|
|
o := originWithPeer(origins, number)
|
|
if o != nil {
|
|
t.leave(prefix, o, number)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load puts one prefix and origin AS pair that the database holds into the
|
|
// table, as startup does with each row it reads. The change's peers join the
|
|
// pair's peer set, and its routes are kept as an announcement's would be.
|
|
// Unlike an update, Load records no change, so TakeChanges does not hand the
|
|
// pair back to be written again. Loading a pair twice changes nothing.
|
|
//
|
|
// The peer set of change holds the peers' numbers from Params.PeerNumbers. A
|
|
// kept route whose peer has no number there is left out, because numbering
|
|
// the peer would record a new peer.
|
|
func (t *Table) Load(change Change) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
// A removed pair has no peers, and a pair without peers is not in the
|
|
// table.
|
|
if change.Peers == nil || change.Peers.None() || !change.Prefix.IsValid() {
|
|
return
|
|
}
|
|
|
|
prefix := change.Prefix.Masked()
|
|
o := t.findOrAddOrigin(prefix, change.OriginASN)
|
|
wasWithoutKeptRoute := o.withoutKeptRoute()
|
|
|
|
for peer := range change.Peers.EachSet() {
|
|
if !o.peers.Test(peer) {
|
|
o.peers.Set(peer)
|
|
|
|
t.familyOf(prefix.Addr()).routes++
|
|
t.peerPrefixes[peer]++
|
|
}
|
|
}
|
|
|
|
// Oldest first, as they were announced, so that two routes with the same
|
|
// time are kept in the order they had.
|
|
for _, r := range slices.Backward(change.Routes) {
|
|
peer, known := t.peerNumbers[r.Peer]
|
|
if !known {
|
|
continue
|
|
}
|
|
|
|
t.dropKeptRoute(o, peer)
|
|
t.keepIfNewest(o, route{
|
|
peer: peer,
|
|
path: slices.Clone(r.ASPath),
|
|
time: r.Time,
|
|
})
|
|
}
|
|
|
|
t.countOriginWithoutKeptRoute(o, wasWithoutKeptRoute)
|
|
}
|
|
|
|
// TakeChanges returns the prefix and origin AS pairs that changed since it last
|
|
// ran, in prefix order, each with its current content. It also returns the
|
|
// peers numbered since then, by address. It then clears both records.
|
|
func (t *Table) TakeChanges() ([]Change, map[string]uint) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
changes := make([]Change, 0, len(t.changed))
|
|
for key := range t.changed {
|
|
changes = append(changes, t.currentContent(key))
|
|
}
|
|
|
|
slices.SortFunc(changes, func(a, b Change) int {
|
|
return cmp.Or(a.Prefix.Compare(b.Prefix),
|
|
cmp.Compare(a.OriginASN, b.OriginASN))
|
|
})
|
|
|
|
newPeers := t.newPeers
|
|
t.newPeers = make(map[string]uint)
|
|
t.changed = make(map[pair]struct{})
|
|
|
|
return changes, newPeers
|
|
}
|
|
|
|
// numberPeer returns the peer's number, and gives it the next one the first
|
|
// time the table sees it.
|
|
func (t *Table) numberPeer(address string) uint {
|
|
number, known := t.peerNumbers[address]
|
|
if known {
|
|
return number
|
|
}
|
|
|
|
number = t.nextPeerNumber
|
|
t.nextPeerNumber++
|
|
t.peerNumbers[address] = number
|
|
t.peerAddresses[number] = address
|
|
t.newPeers[address] = number
|
|
|
|
return number
|
|
}
|
|
|
|
// familyOf returns the part of the table for addr's IP version.
|
|
func (t *Table) familyOf(addr netip.Addr) *family {
|
|
if addr.Is4() {
|
|
return &t.ipv4
|
|
}
|
|
|
|
return &t.ipv6
|
|
}
|
|
|
|
// prefixesOfLength returns the prefixes of the IP version and the mask length
|
|
// of prefix, a valid masked prefix.
|
|
func (t *Table) prefixesOfLength(prefix netip.Prefix) map[netip.Prefix][]*origin {
|
|
return t.familyOf(prefix.Addr()).byLength[prefix.Bits()]
|
|
}
|
|
|
|
// origins returns the origins of prefix, a valid masked prefix.
|
|
func (t *Table) origins(prefix netip.Prefix) []*origin {
|
|
return t.prefixesOfLength(prefix)[prefix]
|
|
}
|
|
|
|
// findOrAddOrigin returns the origin of prefix with AS asn, added with no
|
|
// peers when the prefix has none with that AS.
|
|
func (t *Table) findOrAddOrigin(prefix netip.Prefix, asn int) *origin {
|
|
prefixes := t.prefixesOfLength(prefix)
|
|
origins := prefixes[prefix]
|
|
|
|
i, found := slices.BinarySearchFunc(origins, asn,
|
|
func(o *origin, asn int) int { return cmp.Compare(o.asn, asn) })
|
|
if found {
|
|
return origins[i]
|
|
}
|
|
|
|
o := &origin{asn: asn}
|
|
prefixes[prefix] = slices.Insert(origins, i, o)
|
|
|
|
if t.byOrigin[asn] == nil {
|
|
t.byOrigin[asn] = make(map[netip.Prefix]*origin)
|
|
}
|
|
|
|
t.byOrigin[asn][prefix] = o
|
|
|
|
return o
|
|
}
|
|
|
|
// removeOrigin removes o, which no peer carries any more, from prefix, and the
|
|
// prefix from the table when it has no origin left.
|
|
func (t *Table) removeOrigin(prefix netip.Prefix, o *origin) {
|
|
prefixes := t.prefixesOfLength(prefix)
|
|
|
|
origins := slices.DeleteFunc(prefixes[prefix],
|
|
func(other *origin) bool { return other == o })
|
|
if len(origins) == 0 {
|
|
delete(prefixes, prefix)
|
|
} else {
|
|
prefixes[prefix] = origins
|
|
}
|
|
|
|
delete(t.byOrigin[o.asn], prefix)
|
|
|
|
if len(t.byOrigin[o.asn]) == 0 {
|
|
delete(t.byOrigin, o.asn)
|
|
}
|
|
}
|
|
|
|
// join puts the peer of r in the peer set of o, an origin of prefix, and keeps
|
|
// r in full when it is one of the two newest routes of o. The peer's earlier
|
|
// route, if o keeps one, is replaced: it is no longer the peer's current route.
|
|
// The pair is recorded as changed only when its peer set or kept routes change.
|
|
func (t *Table) join(prefix netip.Prefix, o *origin, r route) {
|
|
if slices.ContainsFunc(o.routes, r.equal) {
|
|
// The peer announced its kept route again.
|
|
return
|
|
}
|
|
|
|
wasWithoutKeptRoute := o.withoutKeptRoute()
|
|
|
|
joined := !o.peers.Test(r.peer)
|
|
if joined {
|
|
o.peers.Set(r.peer)
|
|
|
|
t.familyOf(prefix.Addr()).routes++
|
|
t.peerPrefixes[r.peer]++
|
|
}
|
|
|
|
dropped := t.dropKeptRoute(o, r.peer)
|
|
kept := t.keepIfNewest(o, r)
|
|
|
|
if joined || dropped || kept {
|
|
t.recordChange(prefix, o, wasWithoutKeptRoute)
|
|
}
|
|
}
|
|
|
|
// leave takes peer out of the peer set of o, an origin of prefix, and drops
|
|
// its kept route, if any.
|
|
func (t *Table) leave(prefix netip.Prefix, o *origin, peer uint) {
|
|
wasWithoutKeptRoute := o.withoutKeptRoute()
|
|
|
|
o.peers.Clear(peer)
|
|
t.dropKeptRoute(o, peer)
|
|
|
|
t.familyOf(prefix.Addr()).routes--
|
|
t.peerPrefixes[peer]--
|
|
|
|
t.recordChange(prefix, o, wasWithoutKeptRoute)
|
|
}
|
|
|
|
// keepIfNewest keeps r in o unless both kept routes of o are newer, and says
|
|
// whether it kept r. When o already keeps two routes, the older one is
|
|
// dropped, and its peer keeps only its bit.
|
|
func (t *Table) keepIfNewest(o *origin, r route) bool {
|
|
// The kept routes stay newest first: r goes before the first one that is
|
|
// not newer than it.
|
|
i := 0
|
|
for i < len(o.routes) && o.routes[i].time.After(r.time) {
|
|
i++
|
|
}
|
|
|
|
if i == keptRoutesPerOrigin {
|
|
return false
|
|
}
|
|
|
|
if len(o.routes) == keptRoutesPerOrigin {
|
|
t.dropKeptRoute(o, o.routes[keptRoutesPerOrigin-1].peer)
|
|
}
|
|
|
|
o.routes = slices.Insert(o.routes, i, r)
|
|
t.countKeptTime(r.time, 1)
|
|
|
|
return true
|
|
}
|
|
|
|
// dropKeptRoute drops the peer's route from the kept routes of o, if o keeps
|
|
// one, and says whether it did.
|
|
func (t *Table) dropKeptRoute(o *origin, peer uint) bool {
|
|
i := slices.IndexFunc(o.routes, func(r route) bool { return r.peer == peer })
|
|
if i < 0 {
|
|
return false
|
|
}
|
|
|
|
t.countKeptTime(o.routes[i].time, -1)
|
|
o.routes = slices.Delete(o.routes, i, i+1)
|
|
|
|
return true
|
|
}
|
|
|
|
// recordChange records that the pair of prefix and the AS of o changed, and
|
|
// brings the count of origins without a kept route up to date.
|
|
// wasWithoutKeptRoute says whether o was such an origin before the change. An
|
|
// origin that no peer carries any more is removed.
|
|
func (t *Table) recordChange(
|
|
prefix netip.Prefix, o *origin, wasWithoutKeptRoute bool,
|
|
) {
|
|
t.changed[pair{prefix: prefix, asn: o.asn}] = struct{}{}
|
|
|
|
t.countOriginWithoutKeptRoute(o, wasWithoutKeptRoute)
|
|
|
|
if o.peers.None() {
|
|
t.removeOrigin(prefix, o)
|
|
}
|
|
}
|
|
|
|
// countOriginWithoutKeptRoute brings the count of origins without a kept route
|
|
// up to date after a change to o. wasWithoutKeptRoute says whether o was such
|
|
// an origin before the change.
|
|
func (t *Table) countOriginWithoutKeptRoute(o *origin, wasWithoutKeptRoute bool) {
|
|
if wasWithoutKeptRoute {
|
|
t.originsWithoutKeptRoute--
|
|
}
|
|
|
|
if o.withoutKeptRoute() {
|
|
t.originsWithoutKeptRoute++
|
|
}
|
|
}
|
|
|
|
// countKeptTime adds delta to the number of kept routes with time at.
|
|
func (t *Table) countKeptTime(at time.Time, delta int) {
|
|
count, _ := t.keptTimes.Get(keptTime{time: at})
|
|
count.time = at
|
|
count.routes += delta
|
|
|
|
if count.routes == 0 {
|
|
t.keptTimes.Delete(count)
|
|
|
|
return
|
|
}
|
|
|
|
t.keptTimes.ReplaceOrInsert(count)
|
|
}
|
|
|
|
// currentContent returns what the table holds for the pair now.
|
|
func (t *Table) currentContent(key pair) Change {
|
|
o := t.byOrigin[key.asn][key.prefix]
|
|
if o == nil {
|
|
return Change{Prefix: key.prefix, OriginASN: key.asn, Removed: true}
|
|
}
|
|
|
|
return Change{
|
|
Prefix: key.prefix,
|
|
OriginASN: key.asn,
|
|
Peers: o.peers.Clone(),
|
|
Routes: t.exportRoutes(o.routes),
|
|
}
|
|
}
|
|
|
|
// exportRoutes returns copies of routes that name each peer by its address, or
|
|
// nil when there are none.
|
|
func (t *Table) exportRoutes(routes []route) []Route {
|
|
if len(routes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
exported := make([]Route, 0, len(routes))
|
|
for _, r := range routes {
|
|
exported = append(exported, Route{
|
|
Peer: t.peerAddresses[r.peer],
|
|
ASPath: slices.Clone(r.path),
|
|
Time: r.time,
|
|
})
|
|
}
|
|
|
|
return exported
|
|
}
|
|
|
|
// family is the part of the table for one IP version.
|
|
type family struct {
|
|
// byLength[n] holds the prefixes of mask length n that some peer carries,
|
|
// each with its origins in AS number order.
|
|
byLength []map[netip.Prefix][]*origin
|
|
// minLookupLength is the shortest mask length the address lookup uses.
|
|
minLookupLength int
|
|
// routes counts the peer and prefix pairs: each peer that carries a
|
|
// prefix counts once for it.
|
|
routes int
|
|
}
|
|
|
|
// newFamily returns an empty family for addresses of bits bits.
|
|
func newFamily(bits, minLookupLength int) family {
|
|
f := family{
|
|
byLength: make([]map[netip.Prefix][]*origin, bits+1),
|
|
minLookupLength: minLookupLength,
|
|
}
|
|
|
|
for n := range f.byLength {
|
|
f.byLength[n] = make(map[netip.Prefix][]*origin)
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
// prefixesByLength returns how many prefixes of each mask length f holds.
|
|
func (f *family) prefixesByLength() []int {
|
|
counts := make([]int, len(f.byLength))
|
|
for n, prefixes := range f.byLength {
|
|
counts[n] = len(prefixes)
|
|
}
|
|
|
|
return counts
|
|
}
|
|
|
|
// origin is what the table holds for one prefix and origin AS.
|
|
type origin struct {
|
|
asn int
|
|
// peers holds the number of each peer whose current route to the prefix
|
|
// has this origin AS.
|
|
peers bitset.BitSet
|
|
// routes are the newest of those routes, newest first, at most
|
|
// keptRoutesPerOrigin.
|
|
routes []route
|
|
}
|
|
|
|
// withoutKeptRoute says whether peers carry o but o keeps no route.
|
|
func (o *origin) withoutKeptRoute() bool {
|
|
return o.peers.Any() && len(o.routes) == 0
|
|
}
|
|
|
|
// newestTime returns the time of the newest kept route of o, or nil when o
|
|
// keeps none.
|
|
func (o *origin) newestTime() *time.Time {
|
|
if len(o.routes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
newest := o.routes[0].time
|
|
|
|
return &newest
|
|
}
|
|
|
|
// originWithPeer returns the origin whose peer set holds peer, or nil when
|
|
// none does. A peer carries a prefix with one origin AS at most.
|
|
func originWithPeer(origins []*origin, peer uint) *origin {
|
|
for _, o := range origins {
|
|
if o.peers.Test(peer) {
|
|
return o
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// route is a kept route. peer is the peer's number.
|
|
type route struct {
|
|
peer uint
|
|
path []int
|
|
time time.Time
|
|
}
|
|
|
|
// equal says whether r and other are the same peer's route with the same path
|
|
// and time.
|
|
func (r route) equal(other route) bool {
|
|
return r.peer == other.peer && r.time.Equal(other.time) &&
|
|
slices.Equal(r.path, other.path)
|
|
}
|
|
|
|
// pair names one prefix and origin AS.
|
|
type pair struct {
|
|
prefix netip.Prefix
|
|
asn int
|
|
}
|
|
|
|
// keptTime counts the kept routes with one time.
|
|
type keptTime struct {
|
|
time time.Time
|
|
routes int
|
|
}
|