Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c2882c19c | ||
|
|
1f612642e3 |
No files matched your search
@@ -170,8 +170,8 @@ routewatch/
|
|||||||
```sql
|
```sql
|
||||||
-- Core tables
|
-- Core tables
|
||||||
asns(id, number, handle, description, first_seen, last_seen)
|
asns(id, number, handle, description, first_seen, last_seen)
|
||||||
prefixes_v4(prefix, first_seen)
|
prefixes_v4(id, prefix, mask_length, first_seen, last_seen)
|
||||||
prefixes_v6(prefix, first_seen)
|
prefixes_v6(id, prefix, mask_length, first_seen, last_seen)
|
||||||
|
|
||||||
-- Live routing tables (one per IP version)
|
-- Live routing tables (one per IP version)
|
||||||
live_routes_v4(id, prefix, mask_length, origin_asn, peer_ip, as_path,
|
live_routes_v4(id, prefix, mask_length, origin_asn, peer_ip, as_path,
|
||||||
|
|||||||
@@ -760,11 +760,40 @@ func (d *Database) DeleteLiveRoutesOfPeer(peerIP string) (int, error) {
|
|||||||
return int(deletedV4 + deletedV6), nil
|
return int(deletedV4 + deletedV6), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertPrefixesInTable inserts the prefixes, all of one IP version, that the
|
// upsertPrefix updates the last_seen time of prefix with the statements of its
|
||||||
// prefix table of that version does not hold yet, each first seen at its time
|
// IP version's table, inserting the prefix when the update matched no row. It
|
||||||
// in prefixes. A prefix already stored is left as it is. It returns how many it
|
// reports whether it inserted it.
|
||||||
// inserted.
|
func upsertPrefix(
|
||||||
func insertPrefixesInTable(
|
ctx context.Context, updateStmt, insertStmt *sql.Stmt,
|
||||||
|
prefix string, timestamp time.Time,
|
||||||
|
) (bool, error) {
|
||||||
|
res, err := updateStmt.ExecContext(ctx, timestamp, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to update prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to count updated prefix %s: %w",
|
||||||
|
prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected > 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = insertStmt.ExecContext(ctx, prefix, timestamp, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to insert prefix %s: %w", prefix, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// upsertPrefixesInTable updates the last_seen time of prefixes, all of one IP
|
||||||
|
// version, in the prefix table of that version, inserting the ones that are
|
||||||
|
// new. It returns how many it inserted.
|
||||||
|
func upsertPrefixesInTable(
|
||||||
ctx context.Context, tx *loggingTx, prefixes map[string]time.Time,
|
ctx context.Context, tx *loggingTx, prefixes map[string]time.Time,
|
||||||
ipVersion int,
|
ipVersion int,
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
@@ -773,15 +802,24 @@ func insertPrefixesInTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
family := ipv4Family
|
family := ipv4Family
|
||||||
insertSQL := "INSERT INTO prefixes_v4 (prefix, first_seen) VALUES (?, ?) " +
|
updateSQL := "UPDATE prefixes_v4 SET last_seen = ? WHERE prefix = ?"
|
||||||
"ON CONFLICT (prefix) DO NOTHING"
|
insertSQL := "INSERT INTO prefixes_v4 (prefix, first_seen, last_seen) " +
|
||||||
|
"VALUES (?, ?, ?)"
|
||||||
|
|
||||||
if ipVersion == ipVersionV6 {
|
if ipVersion == ipVersionV6 {
|
||||||
family = ipv6Family
|
family = ipv6Family
|
||||||
insertSQL = "INSERT INTO prefixes_v6 (prefix, first_seen) VALUES (?, ?) " +
|
updateSQL = "UPDATE prefixes_v6 SET last_seen = ? WHERE prefix = ?"
|
||||||
"ON CONFLICT (prefix) DO NOTHING"
|
insertSQL = "INSERT INTO prefixes_v6 (prefix, first_seen, last_seen) " +
|
||||||
|
"VALUES (?, ?, ?)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStmt, err := tx.PrepareContext(ctx, updateSQL)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to prepare %s update statement: %w",
|
||||||
|
family, err)
|
||||||
|
}
|
||||||
|
defer func() { _ = updateStmt.Close() }()
|
||||||
|
|
||||||
insertStmt, err := tx.PrepareContext(ctx, insertSQL)
|
insertStmt, err := tx.PrepareContext(ctx, insertSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to prepare %s insert statement: %w",
|
return 0, fmt.Errorf("failed to prepare %s insert statement: %w",
|
||||||
@@ -793,19 +831,13 @@ func insertPrefixesInTable(
|
|||||||
|
|
||||||
// The rows are written in key order, as UpsertLiveRouteBatch writes routes.
|
// The rows are written in key order, as UpsertLiveRouteBatch writes routes.
|
||||||
for _, prefix := range slices.Sorted(maps.Keys(prefixes)) {
|
for _, prefix := range slices.Sorted(maps.Keys(prefixes)) {
|
||||||
res, err := insertStmt.ExecContext(ctx, prefix, prefixes[prefix])
|
inserted, err := upsertPrefix(ctx, updateStmt, insertStmt, prefix,
|
||||||
|
prefixes[prefix])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to insert prefix %s: %w", prefix, err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// One row for a new prefix, none for one already stored.
|
if inserted {
|
||||||
inserted, err := res.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to count inserted prefix %s: %w",
|
|
||||||
prefix, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inserted > 0 {
|
|
||||||
newPrefixes++
|
newPrefixes++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -813,9 +845,8 @@ func insertPrefixesInTable(
|
|||||||
return newPrefixes, nil
|
return newPrefixes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePrefixesBatch stores, in a single transaction, the prefixes that are
|
// UpdatePrefixesBatch updates the last_seen time for multiple prefixes in a
|
||||||
// not stored yet, each first seen at its time in prefixes: the IPv4 prefixes
|
// single transaction, the IPv4 prefixes first and then the IPv6 prefixes.
|
||||||
// first and then the IPv6 prefixes.
|
|
||||||
func (d *Database) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
func (d *Database) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
||||||
if len(prefixes) == 0 {
|
if len(prefixes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -843,12 +874,12 @@ func (d *Database) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
newV4, err := insertPrefixesInTable(ctx, tx, prefixesV4, ipVersionV4)
|
newV4, err := upsertPrefixesInTable(ctx, tx, prefixesV4, ipVersionV4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newV6, err := insertPrefixesInTable(ctx, tx, prefixesV6, ipVersionV6)
|
newV6, err := upsertPrefixesInTable(ctx, tx, prefixesV6, ipVersionV6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1081,13 +1112,30 @@ func (d *Database) GetOrCreatePrefix(
|
|||||||
|
|
||||||
var p Prefix
|
var p Prefix
|
||||||
|
|
||||||
query := fmt.Sprintf("SELECT prefix, first_seen FROM %s WHERE prefix = ?",
|
query := fmt.Sprintf("SELECT prefix, first_seen, last_seen "+
|
||||||
tableName)
|
"FROM %s WHERE prefix = ?", tableName)
|
||||||
|
|
||||||
err = tx.QueryRow(query, prefix).Scan(&p.Prefix, &p.FirstSeen)
|
err = tx.QueryRow(query, prefix).Scan(&p.Prefix, &p.FirstSeen, &p.LastSeen)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Prefix exists; the deferred rollback ends the read.
|
// Prefix exists, update last_seen
|
||||||
p.IPVersion = ipVersion
|
p.IPVersion = ipVersion
|
||||||
|
updateQuery := fmt.Sprintf("UPDATE %s SET last_seen = ? WHERE prefix = ?",
|
||||||
|
tableName)
|
||||||
|
|
||||||
|
_, err = tx.Exec(updateQuery, timestamp, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.LastSeen = timestamp
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
d.logger.Error("Failed to commit transaction for prefix update",
|
||||||
|
"prefix", prefix, "error", err)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
@@ -1097,11 +1145,12 @@ func (d *Database) GetOrCreatePrefix(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prefix doesn't exist, create it
|
// Prefix doesn't exist, create it
|
||||||
p = Prefix{Prefix: prefix, IPVersion: ipVersion, FirstSeen: timestamp}
|
p = Prefix{Prefix: prefix, IPVersion: ipVersion,
|
||||||
|
FirstSeen: timestamp, LastSeen: timestamp}
|
||||||
insertQuery := fmt.Sprintf("INSERT INTO %s "+
|
insertQuery := fmt.Sprintf("INSERT INTO %s "+
|
||||||
"(prefix, first_seen) VALUES (?, ?)", tableName)
|
"(prefix, first_seen, last_seen) VALUES (?, ?, ?)", tableName)
|
||||||
|
|
||||||
_, err = tx.Exec(insertQuery, p.Prefix, p.FirstSeen)
|
_, err = tx.Exec(insertQuery, p.Prefix, p.FirstSeen, p.LastSeen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1218,8 +1218,8 @@ func TestLiveRouteUpdateSetsOriginWhenItChanges(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestUpdatePrefixesBatchKeepsFirstSeen writes a prefix, then a batch with it
|
// TestUpdatePrefixesBatchKeepsFirstSeen writes a prefix, then a batch with it
|
||||||
// and a new prefix. Only the new prefix is inserted and counted, and the known
|
// and a new prefix. Only the new prefix is counted, and the known one keeps its
|
||||||
// one keeps its first_seen time.
|
// first_seen time while its last_seen time moves on.
|
||||||
func TestUpdatePrefixesBatchKeepsFirstSeen(t *testing.T) {
|
func TestUpdatePrefixesBatchKeepsFirstSeen(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -1257,29 +1257,29 @@ func TestUpdatePrefixesBatchKeepsFirstSeen(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
query, prefix string
|
query, prefix string
|
||||||
firstSeen time.Time
|
firstSeen, lastSeen time.Time
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
query: "SELECT first_seen FROM prefixes_v4 WHERE prefix = ?",
|
query: "SELECT first_seen, last_seen FROM prefixes_v4 WHERE prefix = ?",
|
||||||
prefix: known, firstSeen: first,
|
prefix: known, firstSeen: first, lastSeen: later,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
query: "SELECT first_seen FROM prefixes_v6 WHERE prefix = ?",
|
query: "SELECT first_seen, last_seen FROM prefixes_v6 WHERE prefix = ?",
|
||||||
prefix: testV6Prefix, firstSeen: later,
|
prefix: testV6Prefix, firstSeen: later, lastSeen: later,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
var firstSeen time.Time
|
var firstSeen, lastSeen time.Time
|
||||||
|
|
||||||
err := db.db.QueryRowContext(t.Context(), tt.query, tt.prefix).
|
err := db.db.QueryRowContext(t.Context(), tt.query, tt.prefix).
|
||||||
Scan(&firstSeen)
|
Scan(&firstSeen, &lastSeen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("reading %s: %v", tt.prefix, err)
|
t.Fatalf("reading %s: %v", tt.prefix, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !firstSeen.Equal(tt.firstSeen) {
|
if !firstSeen.Equal(tt.firstSeen) || !lastSeen.Equal(tt.lastSeen) {
|
||||||
t.Errorf("%s: first seen %s, want %s", tt.prefix, firstSeen,
|
t.Errorf("%s: first seen %s, last seen %s; want %s, %s", tt.prefix,
|
||||||
tt.firstSeen)
|
firstSeen, lastSeen, tt.firstSeen, tt.lastSeen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1489,8 +1489,8 @@ func benchRoute(rng *rand.Rand, prefix, peer int, ts time.Time) *LiveRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeBenchBatch writes what the prefix handler writes for a batch: the
|
// writeBenchBatch writes what the prefix handler writes for a batch: the
|
||||||
// announced routes, the withdrawals, and every prefix the batch names, of
|
// announced routes, the withdrawals, and the last-seen time of every prefix
|
||||||
// which only the ones not stored yet are inserted.
|
// the batch names.
|
||||||
func writeBenchBatch(
|
func writeBenchBatch(
|
||||||
b *testing.B, db *Database, routes []*LiveRoute,
|
b *testing.B, db *Database, routes []*LiveRoute,
|
||||||
withdrawals []LiveRouteDeletion, ts time.Time,
|
withdrawals []LiveRouteDeletion, ts time.Time,
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ type ASN struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prefix represents an IP prefix (CIDR block) with its IP version (4 or 6)
|
// Prefix represents an IP prefix (CIDR block) with its IP version (4 or 6)
|
||||||
// and the time it was first seen.
|
// and first/last seen timestamps.
|
||||||
type Prefix struct {
|
type Prefix struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
IPVersion int // 4 or 6
|
IPVersion int // 4 or 6
|
||||||
FirstSeen time.Time
|
FirstSeen time.Time
|
||||||
|
LastSeen time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Announcement represents a BGP announcement or withdrawal event,
|
// Announcement represents a BGP announcement or withdrawal event,
|
||||||
|
|||||||
@@ -31,13 +31,15 @@ CREATE TABLE IF NOT EXISTS asns (
|
|||||||
-- rows are stored by prefix (WITHOUT ROWID) and need no index of their own.
|
-- rows are stored by prefix (WITHOUT ROWID) and need no index of their own.
|
||||||
CREATE TABLE IF NOT EXISTS prefixes_v4 (
|
CREATE TABLE IF NOT EXISTS prefixes_v4 (
|
||||||
prefix TEXT PRIMARY KEY,
|
prefix TEXT PRIMARY KEY,
|
||||||
first_seen DATETIME NOT NULL
|
first_seen DATETIME NOT NULL,
|
||||||
|
last_seen DATETIME NOT NULL
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
-- IPv6 prefixes table, stored as prefixes_v4 is.
|
-- IPv6 prefixes table, stored as prefixes_v4 is.
|
||||||
CREATE TABLE IF NOT EXISTS prefixes_v6 (
|
CREATE TABLE IF NOT EXISTS prefixes_v6 (
|
||||||
prefix TEXT PRIMARY KEY,
|
prefix TEXT PRIMARY KEY,
|
||||||
first_seen DATETIME NOT NULL
|
first_seen DATETIME NOT NULL,
|
||||||
|
last_seen DATETIME NOT NULL
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS announcements (
|
CREATE TABLE IF NOT EXISTS announcements (
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ func (m *mockStore) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
|||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
for prefix, timestamp := range prefixes {
|
for prefix, timestamp := range prefixes {
|
||||||
if _, exists := m.Prefixes[prefix]; !exists {
|
if p, exists := m.Prefixes[prefix]; exists {
|
||||||
|
p.LastSeen = timestamp
|
||||||
|
} else {
|
||||||
const (
|
const (
|
||||||
ipVersionV4 = 4
|
ipVersionV4 = 4
|
||||||
ipVersionV6 = 6
|
ipVersionV6 = 6
|
||||||
@@ -97,6 +99,7 @@ func (m *mockStore) UpdatePrefixesBatch(prefixes map[string]time.Time) error {
|
|||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
FirstSeen: timestamp,
|
FirstSeen: timestamp,
|
||||||
|
LastSeen: timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,8 @@ func (m *mockStore) GetOrCreatePrefix(
|
|||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
if p, exists := m.Prefixes[prefix]; exists {
|
if p, exists := m.Prefixes[prefix]; exists {
|
||||||
|
p.LastSeen = timestamp
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +134,7 @@ func (m *mockStore) GetOrCreatePrefix(
|
|||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
FirstSeen: timestamp,
|
FirstSeen: timestamp,
|
||||||
|
LastSeen: timestamp,
|
||||||
}
|
}
|
||||||
m.Prefixes[prefix] = p
|
m.Prefixes[prefix] = p
|
||||||
m.PrefixCount++
|
m.PrefixCount++
|
||||||
@@ -364,6 +370,7 @@ func (m *mockStore) UpsertLiveRouteBatch(routes []*database.LiveRoute) error {
|
|||||||
Prefix: route.Prefix,
|
Prefix: route.Prefix,
|
||||||
IPVersion: route.IPVersion,
|
IPVersion: route.IPVersion,
|
||||||
FirstSeen: route.LastUpdated,
|
FirstSeen: route.LastUpdated,
|
||||||
|
LastSeen: route.LastUpdated,
|
||||||
}
|
}
|
||||||
m.PrefixCount++
|
m.PrefixCount++
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user