Store the routing table in the database (part of #122)
check / check (push) Canceled after 0s
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
This commit is contained in:
@@ -218,6 +218,57 @@ func (t *Table) PeerDown(peer string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -413,6 +464,17 @@ func (t *Table) recordChange(
|
||||
) {
|
||||
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--
|
||||
}
|
||||
@@ -420,10 +482,6 @@ func (t *Table) recordChange(
|
||||
if o.withoutKeptRoute() {
|
||||
t.originsWithoutKeptRoute++
|
||||
}
|
||||
|
||||
if o.peers.None() {
|
||||
t.removeOrigin(prefix, o)
|
||||
}
|
||||
}
|
||||
|
||||
// countKeptTime adds delta to the number of kept routes with time at.
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
peerA = "192.0.2.1"
|
||||
peerB = "192.0.2.2"
|
||||
peerC = "2001:db8::3"
|
||||
peerD = "2001:db8::4"
|
||||
|
||||
asA = 64500
|
||||
asB = 64501
|
||||
@@ -475,6 +476,77 @@ func TestTakeChangesHoldsOnlyPairsThatChanged(t *testing.T) {
|
||||
checkChanges(t, table, []change{}, map[string]uint{})
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const otherV4 = "10.9.0.0/16"
|
||||
|
||||
v4 := netip.MustParsePrefix(prefixV4)
|
||||
v6 := netip.MustParsePrefix(prefixV6)
|
||||
other := netip.MustParsePrefix(otherV4)
|
||||
|
||||
// The routes of peerC and peerB to v4 are kept; peerA keeps only its bit.
|
||||
// The two routes to v6 have the same time. The routes of other were
|
||||
// withdrawn while peerA still carries it.
|
||||
source := routetable.New(routetable.Params{})
|
||||
announceAll(source, prefixV4, asA, 10, peerA, peerB, peerC)
|
||||
announceAll(source, prefixV4, asB, 40, peerD)
|
||||
source.Announce(v6, asA, routeOf(peerA, asA, 50))
|
||||
source.Announce(v6, asA, routeOf(peerC, asA, 50))
|
||||
announceAll(source, otherV4, asB, 10, peerA, peerB, peerC)
|
||||
source.Withdraw(other, peerB)
|
||||
source.Withdraw(other, peerC)
|
||||
|
||||
changes, peers := source.TakeChanges()
|
||||
loaded := routetable.New(routetable.Params{PeerNumbers: peers})
|
||||
|
||||
// Loading a pair twice changes nothing.
|
||||
for range 2 {
|
||||
for _, change := range changes {
|
||||
loaded.Load(change)
|
||||
}
|
||||
}
|
||||
|
||||
for _, prefix := range []netip.Prefix{v4, v6, other} {
|
||||
got, want := loaded.Origins(prefix), source.Origins(prefix)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Origins(%s) = %+v after the load, want %+v", prefix, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(loaded.Counts(), source.Counts()) {
|
||||
t.Errorf("Counts() = %+v after the load, want %+v",
|
||||
loaded.Counts(), source.Counts())
|
||||
}
|
||||
|
||||
checkChanges(t, loaded, []change{}, map[string]uint{})
|
||||
}
|
||||
|
||||
func TestLoadLeavesOutTheRouteOfAPeerWithoutANumber(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prefix := netip.MustParsePrefix(prefixV4)
|
||||
source := routetable.New(routetable.Params{})
|
||||
announceAll(source, prefixV4, asA, 10, peerA, peerB)
|
||||
|
||||
// peerB has no number in the loaded table.
|
||||
changes, _ := source.TakeChanges()
|
||||
loaded := routetable.New(routetable.Params{
|
||||
PeerNumbers: map[string]uint{peerA: 0},
|
||||
})
|
||||
loaded.Load(changes[0])
|
||||
|
||||
want := []routetable.Route{routeOf(peerA, asA, 10)}
|
||||
|
||||
origins := loaded.Origins(prefix)
|
||||
if len(origins) != 1 || !reflect.DeepEqual(origins[0].Routes, want) {
|
||||
t.Errorf("Origins(%s) = %+v, want one origin with routes %+v",
|
||||
prefix, origins, want)
|
||||
}
|
||||
|
||||
checkChanges(t, loaded, []change{}, map[string]uint{})
|
||||
}
|
||||
|
||||
// TestConcurrentUse has goroutines update one table while others read it. The
|
||||
// race detector, which make test runs, fails it when a method uses the table
|
||||
// without its lock.
|
||||
|
||||
Reference in New Issue
Block a user