Identify a live route by prefix and peer (closes #55)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A peer has at most one route per prefix, but both live route tables were unique on prefix, origin AS and peer. A peer that changed a prefix's origin AS left its old route behind, and a withdrawal sent in the same message as announcements was given their origin AS and missed the withdrawn route. Both tables are now unique on prefix and peer. An announcement replaces the peer's row for the prefix, origin AS included; a withdrawal deletes it without looking at an origin AS. The route counts and prefix distribution kept in memory stay exact, for IPv4 and IPv6. Model: opus-5-5
This commit was merged in pull request #82.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// tempStoreMemory is the PRAGMA temp_store value meaning "hold temp B-trees in
|
||||
@@ -122,8 +123,8 @@ func TestGetIPInfoFindsMostSpecificLiveRoute(t *testing.T) {
|
||||
lookup("2001:db9::1", "")
|
||||
|
||||
err = db.DeleteLiveRouteBatch([]LiveRouteDeletion{
|
||||
{Prefix: "10.1.2.0/24", OriginASN: 64502, PeerIP: testPeerIP, IPVersion: ipVersionV4},
|
||||
{Prefix: "2001:db8:1:2::/64", OriginASN: 64502, PeerIP: testPeerIP, IPVersion: ipVersionV6},
|
||||
{Prefix: "10.1.2.0/24", PeerIP: testPeerIP, IPVersion: ipVersionV4},
|
||||
{Prefix: "2001:db8:1:2::/64", PeerIP: testPeerIP, IPVersion: ipVersionV6},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteLiveRouteBatch: %v", err)
|
||||
@@ -205,6 +206,40 @@ func TestGetIPInfoIgnoresShortRoutes(t *testing.T) {
|
||||
lookup("2001:db8::1", "2001:db8::/32", 64502)
|
||||
}
|
||||
|
||||
// TestLiveRouteTablesHoldOneRoutePerPrefixAndPeer checks that each live route
|
||||
// table refuses a second route from a peer for a prefix, even with another
|
||||
// origin AS. An upsert updates the peer's route before it would insert one, so
|
||||
// only an insert straight into the table shows what the table allows.
|
||||
func TestLiveRouteTablesHoldOneRoutePerPrefixAndPeer(t *testing.T) {
|
||||
db, err := New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
const otherOriginASN = 64501
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
for _, tt := range []struct {
|
||||
route *LiveRoute
|
||||
insertSQL string
|
||||
}{
|
||||
{route: mkV4Route(t, "198.51.100.0/24", 64500, ts), insertSQL: insertLiveRouteV4SQL},
|
||||
{route: mkV6Route("2001:db8::/32", 64500, ts), insertSQL: insertLiveRouteV6SQL},
|
||||
} {
|
||||
if err := db.UpsertLiveRoute(tt.route); err != nil {
|
||||
t.Fatalf("UpsertLiveRoute(%s): %v", tt.route.Prefix, err)
|
||||
}
|
||||
|
||||
_, err := db.db.Exec(tt.insertSQL, uuid.NewString(), tt.route.Prefix, tt.route.MaskLength,
|
||||
otherOriginASN, tt.route.PeerIP, "[]", tt.route.NextHop, ts)
|
||||
var sqliteErr sqlite3.Error
|
||||
if !errors.As(err, &sqliteErr) || sqliteErr.ExtendedCode != sqlite3.ErrConstraintUnique {
|
||||
t.Errorf("second route from peer %s for %s: error %v, want a UNIQUE constraint error",
|
||||
tt.route.PeerIP, tt.route.Prefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectionPoolPragmas holds several pooled connections open at once and
|
||||
// checks each one carries the per-connection settings from the DSN, plus the
|
||||
// process-wide hard heap limit, and sees the new file with auto_vacuum
|
||||
@@ -306,7 +341,6 @@ func TestVacuumReturnsFreePages(t *testing.T) {
|
||||
routes = append(routes, route)
|
||||
deletions = append(deletions, LiveRouteDeletion{
|
||||
Prefix: route.Prefix,
|
||||
OriginASN: asn,
|
||||
PeerIP: route.PeerIP,
|
||||
IPVersion: ipVersionV4,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user