- Remove live_routes table from SQL schema and all related indexes - Create new internal/routingtable package with thread-safe RoutingTable - Implement RouteKey-based indexing with secondary indexes for efficient lookups - Add RoutingTableHandler to manage in-memory routes separately from database - Update DatabaseHandler to only handle persistent database operations - Wire up RoutingTable through fx dependency injection - Update server to get live route count from routing table instead of database - Remove LiveRoutes field from database.Stats struct - Update tests to work with new architecture
300 lines
7.4 KiB
Go
300 lines
7.4 KiB
Go
// Package routingtable provides a thread-safe in-memory representation of the DFZ routing table.
|
|
package routingtable
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Route represents a single route entry in the routing table
|
|
type Route struct {
|
|
PrefixID uuid.UUID `json:"prefix_id"`
|
|
Prefix string `json:"prefix"` // The actual prefix string (e.g., "10.0.0.0/8")
|
|
OriginASNID uuid.UUID `json:"origin_asn_id"`
|
|
OriginASN int `json:"origin_asn"` // The actual ASN number
|
|
PeerASN int `json:"peer_asn"`
|
|
ASPath []int `json:"as_path"` // Full AS path
|
|
NextHop string `json:"next_hop"`
|
|
AnnouncedAt time.Time `json:"announced_at"`
|
|
}
|
|
|
|
// RouteKey uniquely identifies a route in the table
|
|
type RouteKey struct {
|
|
PrefixID uuid.UUID
|
|
OriginASNID uuid.UUID
|
|
PeerASN int
|
|
}
|
|
|
|
// RoutingTable is a thread-safe in-memory routing table
|
|
type RoutingTable struct {
|
|
mu sync.RWMutex
|
|
routes map[RouteKey]*Route
|
|
|
|
// Secondary indexes for efficient lookups
|
|
byPrefix map[uuid.UUID]map[RouteKey]*Route // Routes indexed by prefix ID
|
|
byOriginASN map[uuid.UUID]map[RouteKey]*Route // Routes indexed by origin ASN ID
|
|
byPeerASN map[int]map[RouteKey]*Route // Routes indexed by peer ASN
|
|
}
|
|
|
|
// New creates a new empty routing table
|
|
func New() *RoutingTable {
|
|
return &RoutingTable{
|
|
routes: make(map[RouteKey]*Route),
|
|
byPrefix: make(map[uuid.UUID]map[RouteKey]*Route),
|
|
byOriginASN: make(map[uuid.UUID]map[RouteKey]*Route),
|
|
byPeerASN: make(map[int]map[RouteKey]*Route),
|
|
}
|
|
}
|
|
|
|
// AddRoute adds or updates a route in the routing table
|
|
func (rt *RoutingTable) AddRoute(route *Route) {
|
|
rt.mu.Lock()
|
|
defer rt.mu.Unlock()
|
|
|
|
key := RouteKey{
|
|
PrefixID: route.PrefixID,
|
|
OriginASNID: route.OriginASNID,
|
|
PeerASN: route.PeerASN,
|
|
}
|
|
|
|
// If route already exists, remove it from indexes first
|
|
if existingRoute, exists := rt.routes[key]; exists {
|
|
rt.removeFromIndexes(key, existingRoute)
|
|
}
|
|
|
|
// Add to main map
|
|
rt.routes[key] = route
|
|
|
|
// Update indexes
|
|
rt.addToIndexes(key, route)
|
|
}
|
|
|
|
// RemoveRoute removes a route from the routing table
|
|
func (rt *RoutingTable) RemoveRoute(prefixID, originASNID uuid.UUID, peerASN int) bool {
|
|
rt.mu.Lock()
|
|
defer rt.mu.Unlock()
|
|
|
|
key := RouteKey{
|
|
PrefixID: prefixID,
|
|
OriginASNID: originASNID,
|
|
PeerASN: peerASN,
|
|
}
|
|
|
|
route, exists := rt.routes[key]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from indexes
|
|
rt.removeFromIndexes(key, route)
|
|
|
|
// Remove from main map
|
|
delete(rt.routes, key)
|
|
|
|
return true
|
|
}
|
|
|
|
// WithdrawRoutesByPrefixAndPeer removes all routes for a specific prefix from a specific peer
|
|
func (rt *RoutingTable) WithdrawRoutesByPrefixAndPeer(prefixID uuid.UUID, peerASN int) int {
|
|
rt.mu.Lock()
|
|
defer rt.mu.Unlock()
|
|
|
|
count := 0
|
|
|
|
// Find all routes for this prefix
|
|
if prefixRoutes, exists := rt.byPrefix[prefixID]; exists {
|
|
// Collect keys to delete (can't delete while iterating)
|
|
var keysToDelete []RouteKey
|
|
for key, route := range prefixRoutes {
|
|
if route.PeerASN == peerASN {
|
|
keysToDelete = append(keysToDelete, key)
|
|
}
|
|
}
|
|
|
|
// Delete the routes
|
|
for _, key := range keysToDelete {
|
|
if route, exists := rt.routes[key]; exists {
|
|
rt.removeFromIndexes(key, route)
|
|
delete(rt.routes, key)
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// GetRoute retrieves a specific route
|
|
func (rt *RoutingTable) GetRoute(prefixID, originASNID uuid.UUID, peerASN int) (*Route, bool) {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
key := RouteKey{
|
|
PrefixID: prefixID,
|
|
OriginASNID: originASNID,
|
|
PeerASN: peerASN,
|
|
}
|
|
|
|
route, exists := rt.routes[key]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
// Return a copy to prevent external modification
|
|
routeCopy := *route
|
|
|
|
return &routeCopy, true
|
|
}
|
|
|
|
// GetRoutesByPrefix returns all routes for a specific prefix
|
|
func (rt *RoutingTable) GetRoutesByPrefix(prefixID uuid.UUID) []*Route {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
routes := make([]*Route, 0)
|
|
if prefixRoutes, exists := rt.byPrefix[prefixID]; exists {
|
|
for _, route := range prefixRoutes {
|
|
routeCopy := *route
|
|
routes = append(routes, &routeCopy)
|
|
}
|
|
}
|
|
|
|
return routes
|
|
}
|
|
|
|
// GetRoutesByOriginASN returns all routes originated by a specific ASN
|
|
func (rt *RoutingTable) GetRoutesByOriginASN(originASNID uuid.UUID) []*Route {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
routes := make([]*Route, 0)
|
|
if asnRoutes, exists := rt.byOriginASN[originASNID]; exists {
|
|
for _, route := range asnRoutes {
|
|
routeCopy := *route
|
|
routes = append(routes, &routeCopy)
|
|
}
|
|
}
|
|
|
|
return routes
|
|
}
|
|
|
|
// GetRoutesByPeerASN returns all routes received from a specific peer ASN
|
|
func (rt *RoutingTable) GetRoutesByPeerASN(peerASN int) []*Route {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
routes := make([]*Route, 0)
|
|
if peerRoutes, exists := rt.byPeerASN[peerASN]; exists {
|
|
for _, route := range peerRoutes {
|
|
routeCopy := *route
|
|
routes = append(routes, &routeCopy)
|
|
}
|
|
}
|
|
|
|
return routes
|
|
}
|
|
|
|
// GetAllRoutes returns all active routes in the routing table
|
|
func (rt *RoutingTable) GetAllRoutes() []*Route {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
routes := make([]*Route, 0, len(rt.routes))
|
|
for _, route := range rt.routes {
|
|
routeCopy := *route
|
|
routes = append(routes, &routeCopy)
|
|
}
|
|
|
|
return routes
|
|
}
|
|
|
|
// Size returns the total number of routes in the table
|
|
func (rt *RoutingTable) Size() int {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
return len(rt.routes)
|
|
}
|
|
|
|
// Stats returns statistics about the routing table
|
|
func (rt *RoutingTable) Stats() map[string]int {
|
|
rt.mu.RLock()
|
|
defer rt.mu.RUnlock()
|
|
|
|
stats := map[string]int{
|
|
"total_routes": len(rt.routes),
|
|
"unique_prefixes": len(rt.byPrefix),
|
|
"unique_origins": len(rt.byOriginASN),
|
|
"unique_peers": len(rt.byPeerASN),
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// Clear removes all routes from the routing table
|
|
func (rt *RoutingTable) Clear() {
|
|
rt.mu.Lock()
|
|
defer rt.mu.Unlock()
|
|
|
|
rt.routes = make(map[RouteKey]*Route)
|
|
rt.byPrefix = make(map[uuid.UUID]map[RouteKey]*Route)
|
|
rt.byOriginASN = make(map[uuid.UUID]map[RouteKey]*Route)
|
|
rt.byPeerASN = make(map[int]map[RouteKey]*Route)
|
|
}
|
|
|
|
// Helper methods for index management
|
|
|
|
func (rt *RoutingTable) addToIndexes(key RouteKey, route *Route) {
|
|
// Add to prefix index
|
|
if rt.byPrefix[route.PrefixID] == nil {
|
|
rt.byPrefix[route.PrefixID] = make(map[RouteKey]*Route)
|
|
}
|
|
rt.byPrefix[route.PrefixID][key] = route
|
|
|
|
// Add to origin ASN index
|
|
if rt.byOriginASN[route.OriginASNID] == nil {
|
|
rt.byOriginASN[route.OriginASNID] = make(map[RouteKey]*Route)
|
|
}
|
|
rt.byOriginASN[route.OriginASNID][key] = route
|
|
|
|
// Add to peer ASN index
|
|
if rt.byPeerASN[route.PeerASN] == nil {
|
|
rt.byPeerASN[route.PeerASN] = make(map[RouteKey]*Route)
|
|
}
|
|
rt.byPeerASN[route.PeerASN][key] = route
|
|
}
|
|
|
|
func (rt *RoutingTable) removeFromIndexes(key RouteKey, route *Route) {
|
|
// Remove from prefix index
|
|
if prefixRoutes, exists := rt.byPrefix[route.PrefixID]; exists {
|
|
delete(prefixRoutes, key)
|
|
if len(prefixRoutes) == 0 {
|
|
delete(rt.byPrefix, route.PrefixID)
|
|
}
|
|
}
|
|
|
|
// Remove from origin ASN index
|
|
if asnRoutes, exists := rt.byOriginASN[route.OriginASNID]; exists {
|
|
delete(asnRoutes, key)
|
|
if len(asnRoutes) == 0 {
|
|
delete(rt.byOriginASN, route.OriginASNID)
|
|
}
|
|
}
|
|
|
|
// Remove from peer ASN index
|
|
if peerRoutes, exists := rt.byPeerASN[route.PeerASN]; exists {
|
|
delete(peerRoutes, key)
|
|
if len(peerRoutes) == 0 {
|
|
delete(rt.byPeerASN, route.PeerASN)
|
|
}
|
|
}
|
|
}
|
|
|
|
// String returns a string representation of the route key
|
|
func (k RouteKey) String() string {
|
|
return fmt.Sprintf("%s/%s/%d", k.PrefixID, k.OriginASNID, k.PeerASN)
|
|
}
|