Add live routing table with CIDR mask length tracking
- Added new live_routes table with mask_length column for tracking CIDR prefix lengths - Updated PrefixHandler to maintain live routing table with additions and deletions - Added route expiration functionality (5 minute timeout) to in-memory routing table - Added prefix distribution stats showing count of prefixes by mask length - Added IPv4/IPv6 prefix distribution cards to status page - Updated database interface with UpsertLiveRoute, DeleteLiveRoute, and GetPrefixDistribution - Set all handler queue depths to 50000 for consistency - Doubled DBHandler batch size to 32000 for better throughput - Fixed withdrawal handling to delete routes when origin ASN is available
This commit is contained in:
@@ -4,6 +4,7 @@ package database
|
||||
import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -387,5 +388,120 @@ func (d *Database) GetStats() (Stats, error) {
|
||||
stats.FileSizeBytes = fileInfo.Size()
|
||||
}
|
||||
|
||||
// Get live routes count
|
||||
err = d.db.QueryRow("SELECT COUNT(*) FROM live_routes").Scan(&stats.LiveRoutes)
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to count live routes: %w", err)
|
||||
}
|
||||
|
||||
// Get prefix distribution
|
||||
stats.IPv4PrefixDistribution, stats.IPv6PrefixDistribution, err = d.GetPrefixDistribution()
|
||||
if err != nil {
|
||||
// Log but don't fail
|
||||
d.logger.Warn("Failed to get prefix distribution", "error", err)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// UpsertLiveRoute inserts or updates a live route
|
||||
func (d *Database) UpsertLiveRoute(route *LiveRoute) error {
|
||||
query := `
|
||||
INSERT INTO live_routes (id, prefix, mask_length, ip_version, origin_asn, peer_ip, as_path, next_hop, last_updated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(prefix, origin_asn, peer_ip) DO UPDATE SET
|
||||
mask_length = excluded.mask_length,
|
||||
ip_version = excluded.ip_version,
|
||||
as_path = excluded.as_path,
|
||||
next_hop = excluded.next_hop,
|
||||
last_updated = excluded.last_updated
|
||||
`
|
||||
|
||||
// Encode AS path as JSON
|
||||
pathJSON, err := json.Marshal(route.ASPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode AS path: %w", err)
|
||||
}
|
||||
|
||||
_, err = d.db.Exec(query,
|
||||
route.ID.String(),
|
||||
route.Prefix,
|
||||
route.MaskLength,
|
||||
route.IPVersion,
|
||||
route.OriginASN,
|
||||
route.PeerIP,
|
||||
string(pathJSON),
|
||||
route.NextHop,
|
||||
route.LastUpdated,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteLiveRoute deletes a live route
|
||||
// If originASN is 0, deletes all routes for the prefix/peer combination
|
||||
func (d *Database) DeleteLiveRoute(prefix string, originASN int, peerIP string) error {
|
||||
var query string
|
||||
var err error
|
||||
|
||||
if originASN == 0 {
|
||||
// Delete all routes for this prefix from this peer
|
||||
query = `DELETE FROM live_routes WHERE prefix = ? AND peer_ip = ?`
|
||||
_, err = d.db.Exec(query, prefix, peerIP)
|
||||
} else {
|
||||
// Delete specific route
|
||||
query = `DELETE FROM live_routes WHERE prefix = ? AND origin_asn = ? AND peer_ip = ?`
|
||||
_, err = d.db.Exec(query, prefix, originASN, peerIP)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPrefixDistribution returns the distribution of prefixes by mask length
|
||||
func (d *Database) GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error) {
|
||||
// IPv4 distribution
|
||||
query := `
|
||||
SELECT mask_length, COUNT(*) as count
|
||||
FROM live_routes
|
||||
WHERE ip_version = 4
|
||||
GROUP BY mask_length
|
||||
ORDER BY mask_length
|
||||
`
|
||||
rows, err := d.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to query IPv4 distribution: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
var dist PrefixDistribution
|
||||
if err := rows.Scan(&dist.MaskLength, &dist.Count); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to scan IPv4 distribution: %w", err)
|
||||
}
|
||||
ipv4 = append(ipv4, dist)
|
||||
}
|
||||
|
||||
// IPv6 distribution
|
||||
query = `
|
||||
SELECT mask_length, COUNT(*) as count
|
||||
FROM live_routes
|
||||
WHERE ip_version = 6
|
||||
GROUP BY mask_length
|
||||
ORDER BY mask_length
|
||||
`
|
||||
rows, err = d.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to query IPv6 distribution: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
var dist PrefixDistribution
|
||||
if err := rows.Scan(&dist.MaskLength, &dist.Count); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to scan IPv6 distribution: %w", err)
|
||||
}
|
||||
ipv6 = append(ipv6, dist)
|
||||
}
|
||||
|
||||
return ipv4, ipv6, nil
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@ import (
|
||||
|
||||
// Stats contains database statistics
|
||||
type Stats struct {
|
||||
ASNs int
|
||||
Prefixes int
|
||||
IPv4Prefixes int
|
||||
IPv6Prefixes int
|
||||
Peerings int
|
||||
FileSizeBytes int64
|
||||
ASNs int
|
||||
Prefixes int
|
||||
IPv4Prefixes int
|
||||
IPv6Prefixes int
|
||||
Peerings int
|
||||
FileSizeBytes int64
|
||||
LiveRoutes int
|
||||
IPv4PrefixDistribution []PrefixDistribution
|
||||
IPv6PrefixDistribution []PrefixDistribution
|
||||
}
|
||||
|
||||
// Store defines the interface for database operations
|
||||
@@ -34,6 +37,11 @@ type Store interface {
|
||||
// Peer operations
|
||||
UpdatePeer(peerIP string, peerASN int, messageType string, timestamp time.Time) error
|
||||
|
||||
// Live route operations
|
||||
UpsertLiveRoute(route *LiveRoute) error
|
||||
DeleteLiveRoute(prefix string, originASN int, peerIP string) error
|
||||
GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error)
|
||||
|
||||
// Lifecycle
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -45,3 +45,22 @@ type ASNPeering struct {
|
||||
FirstSeen time.Time `json:"first_seen"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
}
|
||||
|
||||
// LiveRoute represents a route in the live routing table
|
||||
type LiveRoute struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Prefix string `json:"prefix"`
|
||||
MaskLength int `json:"mask_length"`
|
||||
IPVersion int `json:"ip_version"`
|
||||
OriginASN int `json:"origin_asn"`
|
||||
PeerIP string `json:"peer_ip"`
|
||||
ASPath []int `json:"as_path"`
|
||||
NextHop string `json:"next_hop"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// PrefixDistribution represents the distribution of prefixes by mask length
|
||||
type PrefixDistribution struct {
|
||||
MaskLength int `json:"mask_length"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
@@ -68,4 +68,24 @@ CREATE INDEX IF NOT EXISTS idx_asns_number ON asns(number);
|
||||
-- Indexes for bgp_peers table
|
||||
CREATE INDEX IF NOT EXISTS idx_bgp_peers_asn ON bgp_peers(peer_asn);
|
||||
CREATE INDEX IF NOT EXISTS idx_bgp_peers_last_seen ON bgp_peers(last_seen);
|
||||
CREATE INDEX IF NOT EXISTS idx_bgp_peers_ip ON bgp_peers(peer_ip);
|
||||
CREATE INDEX IF NOT EXISTS idx_bgp_peers_ip ON bgp_peers(peer_ip);
|
||||
|
||||
-- Live routing table maintained by PrefixHandler
|
||||
CREATE TABLE IF NOT EXISTS live_routes (
|
||||
id TEXT PRIMARY KEY,
|
||||
prefix TEXT NOT NULL,
|
||||
mask_length INTEGER NOT NULL, -- CIDR mask length (0-32 for IPv4, 0-128 for IPv6)
|
||||
ip_version INTEGER NOT NULL, -- 4 or 6
|
||||
origin_asn INTEGER NOT NULL,
|
||||
peer_ip TEXT NOT NULL,
|
||||
as_path TEXT NOT NULL, -- JSON array
|
||||
next_hop TEXT NOT NULL,
|
||||
last_updated DATETIME NOT NULL,
|
||||
UNIQUE(prefix, origin_asn, peer_ip)
|
||||
);
|
||||
|
||||
-- Indexes for live_routes table
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_prefix ON live_routes(prefix);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_mask_length ON live_routes(mask_length);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_ip_version_mask ON live_routes(ip_version, mask_length);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_last_updated ON live_routes(last_updated);
|
||||
Reference in New Issue
Block a user