Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14ef9e5da2 |
@@ -1,8 +1,8 @@
|
||||
// Package routetable holds the whole routing table in memory. For each prefix
|
||||
// and origin AS it keeps the set of peers whose current route that is, and the
|
||||
// two newest of those routes. It applies announcements, withdrawals and peers'
|
||||
// sessions going down, and it answers the route reads the pages and the API
|
||||
// need.
|
||||
// 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 (
|
||||
@@ -326,20 +326,29 @@ func (t *Table) removeOrigin(prefix netip.Prefix, o *origin) {
|
||||
// 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()
|
||||
|
||||
if !o.peers.Test(r.peer) {
|
||||
joined := !o.peers.Test(r.peer)
|
||||
if joined {
|
||||
o.peers.Set(r.peer)
|
||||
|
||||
t.familyOf(prefix.Addr()).routes++
|
||||
t.peerPrefixes[r.peer]++
|
||||
}
|
||||
|
||||
t.dropKeptRoute(o, r.peer)
|
||||
t.keepIfNewest(o, r)
|
||||
dropped := t.dropKeptRoute(o, r.peer)
|
||||
kept := t.keepIfNewest(o, r)
|
||||
|
||||
t.recordChange(prefix, o, wasWithoutKeptRoute)
|
||||
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
|
||||
@@ -356,10 +365,10 @@ func (t *Table) leave(prefix netip.Prefix, o *origin, peer uint) {
|
||||
t.recordChange(prefix, o, wasWithoutKeptRoute)
|
||||
}
|
||||
|
||||
// keepIfNewest keeps r in o unless both kept routes of o are newer. 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) {
|
||||
// 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
|
||||
@@ -368,7 +377,7 @@ func (t *Table) keepIfNewest(o *origin, r route) {
|
||||
}
|
||||
|
||||
if i == keptRoutesPerOrigin {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if len(o.routes) == keptRoutesPerOrigin {
|
||||
@@ -377,18 +386,22 @@ func (t *Table) keepIfNewest(o *origin, r route) {
|
||||
|
||||
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.
|
||||
func (t *Table) dropKeptRoute(o *origin, peer uint) {
|
||||
// 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
|
||||
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
|
||||
@@ -545,6 +558,13 @@ type route struct {
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -425,6 +426,96 @@ func TestTakeChanges(t *testing.T) {
|
||||
}, map[string]uint{})
|
||||
}
|
||||
|
||||
func TestTakeChangesHoldsOnlyPairsThatChanged(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
table := routetable.New(routetable.Params{})
|
||||
v4 := netip.MustParsePrefix(prefixV4)
|
||||
v6 := netip.MustParsePrefix(prefixV6)
|
||||
|
||||
// peerA, peerB and peerC are numbered 0, 1 and 2. The routes of peerC and
|
||||
// peerB to v4 are kept; peerA keeps only its bit.
|
||||
announceAll(table, prefixV4, asA, 10, peerA, peerB, peerC)
|
||||
table.Announce(v6, asA, routeOf(peerB, asA, 10))
|
||||
table.TakeChanges()
|
||||
|
||||
// peerA announces again, with a time older than both kept routes.
|
||||
table.Announce(v4, asA, routeOf(peerA, asA, 15))
|
||||
checkChanges(t, table, []change{}, map[string]uint{})
|
||||
|
||||
// peerC announces its kept route again.
|
||||
table.Announce(v4, asA, routeOf(peerC, asA, 30))
|
||||
checkChanges(t, table, []change{}, map[string]uint{})
|
||||
|
||||
// peerC announces the same path with a newer time, so its kept route
|
||||
// changes.
|
||||
table.Announce(v4, asA, routeOf(peerC, asA, 40))
|
||||
checkChanges(t, table, []change{
|
||||
{Prefix: prefixV4, OriginASN: asA, Peers: []uint{0, 1, 2},
|
||||
Routes: []routetable.Route{
|
||||
routeOf(peerC, asA, 40), routeOf(peerB, asA, 20),
|
||||
}},
|
||||
}, map[string]uint{})
|
||||
|
||||
// peerA withdraws v6, which it does not carry.
|
||||
table.Withdraw(v6, peerA)
|
||||
checkChanges(t, table, []change{}, map[string]uint{})
|
||||
|
||||
// peerA going down changes v4, the one prefix it carries.
|
||||
table.PeerDown(peerA)
|
||||
checkChanges(t, table, []change{
|
||||
{Prefix: prefixV4, OriginASN: asA, Peers: []uint{1, 2},
|
||||
Routes: []routetable.Route{
|
||||
routeOf(peerC, asA, 40), routeOf(peerB, asA, 20),
|
||||
}},
|
||||
}, map[string]uint{})
|
||||
|
||||
// peerA, which carries nothing now, goes down again.
|
||||
table.PeerDown(peerA)
|
||||
checkChanges(t, table, []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.
|
||||
func TestConcurrentUse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
// rounds is how many times each goroutine calls its method.
|
||||
rounds = 1000
|
||||
ipVersion = 4
|
||||
sampleSize = 10
|
||||
)
|
||||
|
||||
table := routetable.New(routetable.Params{})
|
||||
prefix := netip.MustParsePrefix(prefixV4)
|
||||
|
||||
uses := []func(round int){
|
||||
func(round int) { table.Announce(prefix, asA, routeOf(peerA, asA, round)) },
|
||||
func(round int) { table.Announce(prefix, asB, routeOf(peerB, asB, round)) },
|
||||
func(int) { table.Withdraw(prefix, peerA) },
|
||||
func(int) { table.PeerDown(peerB) },
|
||||
func(int) { table.TakeChanges() },
|
||||
func(int) { table.Lookup(prefix.Addr()) },
|
||||
func(int) { table.Origins(prefix) },
|
||||
func(int) { table.OriginatedPrefixes(asA) },
|
||||
func(int) { table.SamplePrefixes(ipVersion, prefix.Bits(), sampleSize) },
|
||||
func(int) { table.Counts() },
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, use := range uses {
|
||||
wg.Go(func() {
|
||||
for round := range rounds {
|
||||
use(round)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// checkChanges checks that TakeChanges returns want and wantPeers.
|
||||
func checkChanges(
|
||||
t *testing.T, table *routetable.Table, want []change,
|
||||
|
||||
Reference in New Issue
Block a user