Separate IPv4 and IPv6 routes into different tables
- Create live_routes_v4 and live_routes_v6 tables - Update all database methods to use appropriate table - Add IP version detection in database queries - Remove filtering by ip_version column for better performance - Fix route count queries that were timing out - Update PrefixHandler to include IP version in deletions
This commit is contained in:
parent
8e12c07396
commit
3673264552
@ -194,26 +194,42 @@ func (d *Database) UpsertLiveRouteBatch(routes []*LiveRoute) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// Use prepared statement for better performance
|
||||
query := `
|
||||
INSERT INTO live_routes (id, prefix, mask_length, ip_version, origin_asn, peer_ip, as_path, next_hop,
|
||||
last_updated, v4_ip_start, v4_ip_end)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
// Prepare statements for both IPv4 and IPv6
|
||||
queryV4 := `
|
||||
INSERT INTO live_routes_v4 (id, prefix, mask_length, origin_asn, peer_ip, as_path, next_hop,
|
||||
last_updated, ip_start, ip_end)
|
||||
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,
|
||||
v4_ip_start = excluded.v4_ip_start,
|
||||
v4_ip_end = excluded.v4_ip_end
|
||||
ip_start = excluded.ip_start,
|
||||
ip_end = excluded.ip_end
|
||||
`
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
queryV6 := `
|
||||
INSERT INTO live_routes_v6 (id, prefix, mask_length, 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,
|
||||
as_path = excluded.as_path,
|
||||
next_hop = excluded.next_hop,
|
||||
last_updated = excluded.last_updated
|
||||
`
|
||||
|
||||
stmtV4, err := tx.Prepare(queryV4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||
return fmt.Errorf("failed to prepare IPv4 statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmt.Close() }()
|
||||
defer func() { _ = stmtV4.Close() }()
|
||||
|
||||
stmtV6, err := tx.Prepare(queryV6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare IPv6 statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmtV6.Close() }()
|
||||
|
||||
for _, route := range routes {
|
||||
// Encode AS path as JSON
|
||||
@ -222,28 +238,38 @@ func (d *Database) UpsertLiveRouteBatch(routes []*LiveRoute) error {
|
||||
return fmt.Errorf("failed to encode AS path: %w", err)
|
||||
}
|
||||
|
||||
// Convert v4_ip_start and v4_ip_end to interface{} for SQL NULL handling
|
||||
var v4Start, v4End interface{}
|
||||
if route.V4IPStart != nil {
|
||||
v4Start = *route.V4IPStart
|
||||
}
|
||||
if route.V4IPEnd != nil {
|
||||
v4End = *route.V4IPEnd
|
||||
}
|
||||
// Use appropriate statement based on IP version
|
||||
if route.IPVersion == ipVersionV4 {
|
||||
// IPv4 routes must have range values
|
||||
if route.V4IPStart == nil || route.V4IPEnd == nil {
|
||||
return fmt.Errorf("IPv4 route %s missing range values", route.Prefix)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(
|
||||
route.ID.String(),
|
||||
route.Prefix,
|
||||
route.MaskLength,
|
||||
route.IPVersion,
|
||||
route.OriginASN,
|
||||
route.PeerIP,
|
||||
string(pathJSON),
|
||||
route.NextHop,
|
||||
route.LastUpdated,
|
||||
v4Start,
|
||||
v4End,
|
||||
)
|
||||
_, err = stmtV4.Exec(
|
||||
route.ID.String(),
|
||||
route.Prefix,
|
||||
route.MaskLength,
|
||||
route.OriginASN,
|
||||
route.PeerIP,
|
||||
string(pathJSON),
|
||||
route.NextHop,
|
||||
route.LastUpdated,
|
||||
*route.V4IPStart,
|
||||
*route.V4IPEnd,
|
||||
)
|
||||
} else {
|
||||
// IPv6 routes
|
||||
_, err = stmtV6.Exec(
|
||||
route.ID.String(),
|
||||
route.Prefix,
|
||||
route.MaskLength,
|
||||
route.OriginASN,
|
||||
route.PeerIP,
|
||||
string(pathJSON),
|
||||
route.NextHop,
|
||||
route.LastUpdated,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upsert route %s: %w", route.Prefix, err)
|
||||
}
|
||||
@ -275,47 +301,62 @@ func (d *Database) DeleteLiveRouteBatch(deletions []LiveRouteDeletion) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// Separate deletions by type and use prepared statements
|
||||
var withOrigin []LiveRouteDeletion
|
||||
var withoutOrigin []LiveRouteDeletion
|
||||
// No longer need to separate deletions since we handle them in the loop below
|
||||
|
||||
// Prepare statements for both IPv4 and IPv6 tables
|
||||
stmtV4WithOrigin, err := tx.Prepare(`DELETE FROM live_routes_v4 WHERE prefix = ? AND origin_asn = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare IPv4 delete with origin statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmtV4WithOrigin.Close() }()
|
||||
|
||||
stmtV4WithoutOrigin, err := tx.Prepare(`DELETE FROM live_routes_v4 WHERE prefix = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare IPv4 delete without origin statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmtV4WithoutOrigin.Close() }()
|
||||
|
||||
stmtV6WithOrigin, err := tx.Prepare(`DELETE FROM live_routes_v6 WHERE prefix = ? AND origin_asn = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare IPv6 delete with origin statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmtV6WithOrigin.Close() }()
|
||||
|
||||
stmtV6WithoutOrigin, err := tx.Prepare(`DELETE FROM live_routes_v6 WHERE prefix = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare IPv6 delete without origin statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmtV6WithoutOrigin.Close() }()
|
||||
|
||||
// Process deletions
|
||||
for _, del := range deletions {
|
||||
if del.OriginASN == 0 {
|
||||
withoutOrigin = append(withoutOrigin, del)
|
||||
var stmt *sql.Stmt
|
||||
|
||||
// Select appropriate statement based on IP version and whether we have origin ASN
|
||||
//nolint:nestif // Clear logic for selecting the right statement
|
||||
if del.IPVersion == ipVersionV4 {
|
||||
if del.OriginASN == 0 {
|
||||
stmt = stmtV4WithoutOrigin
|
||||
} else {
|
||||
stmt = stmtV4WithOrigin
|
||||
}
|
||||
} else {
|
||||
withOrigin = append(withOrigin, del)
|
||||
}
|
||||
}
|
||||
|
||||
// Process deletions with origin ASN
|
||||
if len(withOrigin) > 0 {
|
||||
stmt, err := tx.Prepare(`DELETE FROM live_routes WHERE prefix = ? AND origin_asn = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare delete statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmt.Close() }()
|
||||
|
||||
for _, del := range withOrigin {
|
||||
_, err = stmt.Exec(del.Prefix, del.OriginASN, del.PeerIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete route %s: %w", del.Prefix, err)
|
||||
if del.OriginASN == 0 {
|
||||
stmt = stmtV6WithoutOrigin
|
||||
} else {
|
||||
stmt = stmtV6WithOrigin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process deletions without origin ASN
|
||||
if len(withoutOrigin) > 0 {
|
||||
stmt, err := tx.Prepare(`DELETE FROM live_routes WHERE prefix = ? AND peer_ip = ?`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare delete statement: %w", err)
|
||||
}
|
||||
defer func() { _ = stmt.Close() }()
|
||||
|
||||
for _, del := range withoutOrigin {
|
||||
// Execute deletion
|
||||
if del.OriginASN == 0 {
|
||||
_, err = stmt.Exec(del.Prefix, del.PeerIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete route %s: %w", del.Prefix, err)
|
||||
}
|
||||
} else {
|
||||
_, err = stmt.Exec(del.Prefix, del.OriginASN, del.PeerIP)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete route %s: %w", del.Prefix, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -903,11 +944,10 @@ func (d *Database) GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []Pr
|
||||
// GetPrefixDistributionContext returns the distribution of unique prefixes by mask length with context support
|
||||
func (d *Database) GetPrefixDistributionContext(ctx context.Context) (
|
||||
ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error) {
|
||||
// IPv4 distribution - count unique prefixes, not routes
|
||||
// IPv4 distribution - count unique prefixes from v4 table
|
||||
query := `
|
||||
SELECT mask_length, COUNT(DISTINCT prefix) as count
|
||||
FROM live_routes
|
||||
WHERE ip_version = 4
|
||||
FROM live_routes_v4
|
||||
GROUP BY mask_length
|
||||
ORDER BY mask_length
|
||||
`
|
||||
@ -929,11 +969,10 @@ func (d *Database) GetPrefixDistributionContext(ctx context.Context) (
|
||||
ipv4 = append(ipv4, dist)
|
||||
}
|
||||
|
||||
// IPv6 distribution - count unique prefixes, not routes
|
||||
// IPv6 distribution - count unique prefixes from v6 table
|
||||
query = `
|
||||
SELECT mask_length, COUNT(DISTINCT prefix) as count
|
||||
FROM live_routes
|
||||
WHERE ip_version = 6
|
||||
FROM live_routes_v6
|
||||
GROUP BY mask_length
|
||||
ORDER BY mask_length
|
||||
`
|
||||
@ -965,14 +1004,14 @@ func (d *Database) GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error) {
|
||||
|
||||
// GetLiveRouteCountsContext returns the count of IPv4 and IPv6 routes with context support
|
||||
func (d *Database) GetLiveRouteCountsContext(ctx context.Context) (ipv4Count, ipv6Count int, err error) {
|
||||
// Get IPv4 count
|
||||
err = d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM live_routes WHERE ip_version = 4").Scan(&ipv4Count)
|
||||
// Get IPv4 count from dedicated table
|
||||
err = d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM live_routes_v4").Scan(&ipv4Count)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to count IPv4 routes: %w", err)
|
||||
}
|
||||
|
||||
// Get IPv6 count
|
||||
err = d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM live_routes WHERE ip_version = 6").Scan(&ipv6Count)
|
||||
// Get IPv6 count from dedicated table
|
||||
err = d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM live_routes_v6").Scan(&ipv6Count)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to count IPv6 routes: %w", err)
|
||||
}
|
||||
@ -1007,9 +1046,9 @@ func (d *Database) GetASInfoForIPContext(ctx context.Context, ip string) (*ASInf
|
||||
|
||||
query := `
|
||||
SELECT DISTINCT lr.prefix, lr.mask_length, lr.origin_asn, lr.last_updated, a.handle, a.description
|
||||
FROM live_routes lr
|
||||
FROM live_routes_v4 lr
|
||||
LEFT JOIN asns a ON a.number = lr.origin_asn
|
||||
WHERE lr.ip_version = ? AND lr.v4_ip_start <= ? AND lr.v4_ip_end >= ?
|
||||
WHERE lr.ip_start <= ? AND lr.ip_end >= ?
|
||||
ORDER BY lr.mask_length DESC
|
||||
LIMIT 1
|
||||
`
|
||||
@ -1019,7 +1058,7 @@ func (d *Database) GetASInfoForIPContext(ctx context.Context, ip string) (*ASInf
|
||||
var lastUpdated time.Time
|
||||
var handle, description sql.NullString
|
||||
|
||||
err := d.db.QueryRowContext(ctx, query, ipVersionV4, ipUint, ipUint).Scan(
|
||||
err := d.db.QueryRowContext(ctx, query, ipUint, ipUint).Scan(
|
||||
&prefix, &maskLength, &originASN, &lastUpdated, &handle, &description)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
@ -1044,13 +1083,12 @@ func (d *Database) GetASInfoForIPContext(ctx context.Context, ip string) (*ASInf
|
||||
// For IPv6, use the original method since we don't have range optimization
|
||||
query := `
|
||||
SELECT DISTINCT lr.prefix, lr.mask_length, lr.origin_asn, lr.last_updated, a.handle, a.description
|
||||
FROM live_routes lr
|
||||
FROM live_routes_v6 lr
|
||||
LEFT JOIN asns a ON a.number = lr.origin_asn
|
||||
WHERE lr.ip_version = ?
|
||||
ORDER BY lr.mask_length DESC
|
||||
`
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, ipVersionV6)
|
||||
rows, err := d.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query routes: %w", err)
|
||||
}
|
||||
@ -1179,27 +1217,29 @@ func (d *Database) GetASDetailsContext(ctx context.Context, asn int) (*ASN, []Li
|
||||
asnInfo.Handle = handle.String
|
||||
asnInfo.Description = description.String
|
||||
|
||||
// Get prefixes announced by this AS (unique prefixes with most recent update)
|
||||
query := `
|
||||
SELECT prefix, mask_length, ip_version, MAX(last_updated) as last_updated
|
||||
FROM live_routes
|
||||
// Get prefixes announced by this AS from both tables
|
||||
var allPrefixes []LiveRoute
|
||||
|
||||
// Query IPv4 prefixes
|
||||
queryV4 := `
|
||||
SELECT prefix, mask_length, MAX(last_updated) as last_updated
|
||||
FROM live_routes_v4
|
||||
WHERE origin_asn = ?
|
||||
GROUP BY prefix, mask_length, ip_version
|
||||
GROUP BY prefix, mask_length
|
||||
`
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, asn)
|
||||
rows4, err := d.db.QueryContext(ctx, queryV4, asn)
|
||||
if err != nil {
|
||||
return &asnInfo, nil, fmt.Errorf("failed to query prefixes: %w", err)
|
||||
return &asnInfo, nil, fmt.Errorf("failed to query IPv4 prefixes: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
defer func() { _ = rows4.Close() }()
|
||||
|
||||
var prefixes []LiveRoute
|
||||
for rows.Next() {
|
||||
for rows4.Next() {
|
||||
var route LiveRoute
|
||||
var lastUpdatedStr string
|
||||
err := rows.Scan(&route.Prefix, &route.MaskLength, &route.IPVersion, &lastUpdatedStr)
|
||||
err := rows4.Scan(&route.Prefix, &route.MaskLength, &lastUpdatedStr)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed to scan prefix row", "error", err, "asn", asn)
|
||||
d.logger.Error("Failed to scan IPv4 prefix row", "error", err, "asn", asn)
|
||||
|
||||
continue
|
||||
}
|
||||
@ -1215,10 +1255,50 @@ func (d *Database) GetASDetailsContext(ctx context.Context, asn int) (*ASN, []Li
|
||||
}
|
||||
}
|
||||
route.OriginASN = asn
|
||||
prefixes = append(prefixes, route)
|
||||
route.IPVersion = ipVersionV4
|
||||
allPrefixes = append(allPrefixes, route)
|
||||
}
|
||||
|
||||
return &asnInfo, prefixes, nil
|
||||
// Query IPv6 prefixes
|
||||
queryV6 := `
|
||||
SELECT prefix, mask_length, MAX(last_updated) as last_updated
|
||||
FROM live_routes_v6
|
||||
WHERE origin_asn = ?
|
||||
GROUP BY prefix, mask_length
|
||||
`
|
||||
|
||||
rows6, err := d.db.QueryContext(ctx, queryV6, asn)
|
||||
if err != nil {
|
||||
return &asnInfo, allPrefixes, fmt.Errorf("failed to query IPv6 prefixes: %w", err)
|
||||
}
|
||||
defer func() { _ = rows6.Close() }()
|
||||
|
||||
for rows6.Next() {
|
||||
var route LiveRoute
|
||||
var lastUpdatedStr string
|
||||
err := rows6.Scan(&route.Prefix, &route.MaskLength, &lastUpdatedStr)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed to scan IPv6 prefix row", "error", err, "asn", asn)
|
||||
|
||||
continue
|
||||
}
|
||||
// Parse the timestamp string
|
||||
route.LastUpdated, err = time.Parse("2006-01-02 15:04:05-07:00", lastUpdatedStr)
|
||||
if err != nil {
|
||||
// Try without timezone
|
||||
route.LastUpdated, err = time.Parse("2006-01-02 15:04:05", lastUpdatedStr)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed to parse timestamp", "error", err, "timestamp", lastUpdatedStr)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
route.OriginASN = asn
|
||||
route.IPVersion = ipVersionV6
|
||||
allPrefixes = append(allPrefixes, route)
|
||||
}
|
||||
|
||||
return &asnInfo, allPrefixes, nil
|
||||
}
|
||||
|
||||
// GetPrefixDetails returns detailed information about a prefix
|
||||
@ -1228,14 +1308,28 @@ func (d *Database) GetPrefixDetails(prefix string) ([]LiveRoute, error) {
|
||||
|
||||
// GetPrefixDetailsContext returns detailed information about a prefix with context support
|
||||
func (d *Database) GetPrefixDetailsContext(ctx context.Context, prefix string) ([]LiveRoute, error) {
|
||||
query := `
|
||||
// Determine if it's IPv4 or IPv6 by parsing the prefix
|
||||
_, ipnet, err := net.ParseCIDR(prefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid prefix format: %w", err)
|
||||
}
|
||||
|
||||
tableName := "live_routes_v4"
|
||||
ipVersion := ipVersionV4
|
||||
if ipnet.IP.To4() == nil {
|
||||
tableName = "live_routes_v6"
|
||||
ipVersion = ipVersionV6
|
||||
}
|
||||
|
||||
//nolint:gosec // Table name is hardcoded based on IP version
|
||||
query := fmt.Sprintf(`
|
||||
SELECT lr.origin_asn, lr.peer_ip, lr.as_path, lr.next_hop, lr.last_updated,
|
||||
a.handle, a.description
|
||||
FROM live_routes lr
|
||||
FROM %s lr
|
||||
LEFT JOIN asns a ON a.number = lr.origin_asn
|
||||
WHERE lr.prefix = ?
|
||||
ORDER BY lr.origin_asn, lr.peer_ip
|
||||
`
|
||||
`, tableName)
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, prefix)
|
||||
if err != nil {
|
||||
@ -1263,6 +1357,8 @@ func (d *Database) GetPrefixDetailsContext(ctx context.Context, prefix string) (
|
||||
}
|
||||
|
||||
route.Prefix = prefix
|
||||
route.IPVersion = ipVersion
|
||||
route.MaskLength, _ = ipnet.Mask.Size()
|
||||
routes = append(routes, route)
|
||||
}
|
||||
|
||||
@ -1282,23 +1378,29 @@ func (d *Database) GetRandomPrefixesByLength(maskLength, ipVersion, limit int) (
|
||||
func (d *Database) GetRandomPrefixesByLengthContext(
|
||||
ctx context.Context, maskLength, ipVersion, limit int) ([]LiveRoute, error) {
|
||||
// Select unique prefixes with their most recent route information
|
||||
query := `
|
||||
tableName := "live_routes_v4"
|
||||
if ipVersion == ipVersionV6 {
|
||||
tableName = "live_routes_v6"
|
||||
}
|
||||
|
||||
//nolint:gosec // Table name is hardcoded based on IP version
|
||||
query := fmt.Sprintf(`
|
||||
WITH unique_prefixes AS (
|
||||
SELECT prefix, MAX(last_updated) as max_updated
|
||||
FROM live_routes
|
||||
WHERE mask_length = ? AND ip_version = ?
|
||||
FROM %s
|
||||
WHERE mask_length = ?
|
||||
GROUP BY prefix
|
||||
ORDER BY RANDOM()
|
||||
LIMIT ?
|
||||
)
|
||||
SELECT lr.prefix, lr.mask_length, lr.ip_version, lr.origin_asn, lr.as_path,
|
||||
SELECT lr.prefix, lr.mask_length, lr.origin_asn, lr.as_path,
|
||||
lr.peer_ip, lr.last_updated
|
||||
FROM live_routes lr
|
||||
FROM %s lr
|
||||
INNER JOIN unique_prefixes up ON lr.prefix = up.prefix AND lr.last_updated = up.max_updated
|
||||
WHERE lr.mask_length = ? AND lr.ip_version = ?
|
||||
`
|
||||
WHERE lr.mask_length = ?
|
||||
`, tableName, tableName)
|
||||
|
||||
rows, err := d.db.QueryContext(ctx, query, maskLength, ipVersion, limit, maskLength, ipVersion)
|
||||
rows, err := d.db.QueryContext(ctx, query, maskLength, limit, maskLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query random prefixes: %w", err)
|
||||
}
|
||||
@ -1313,12 +1415,13 @@ func (d *Database) GetRandomPrefixesByLengthContext(
|
||||
err := rows.Scan(
|
||||
&route.Prefix,
|
||||
&route.MaskLength,
|
||||
&route.IPVersion,
|
||||
&route.OriginASN,
|
||||
&pathJSON,
|
||||
&route.PeerIP,
|
||||
&route.LastUpdated,
|
||||
)
|
||||
// Set IP version based on which table we queried
|
||||
route.IPVersion = ipVersion
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ type LiveRouteDeletion struct {
|
||||
Prefix string
|
||||
OriginASN int
|
||||
PeerIP string
|
||||
IPVersion int
|
||||
}
|
||||
|
||||
// PeerUpdate represents parameters for updating a peer
|
||||
|
@ -72,31 +72,50 @@ 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);
|
||||
|
||||
-- Live routing table maintained by PrefixHandler
|
||||
CREATE TABLE IF NOT EXISTS live_routes (
|
||||
-- IPv4 routing table maintained by PrefixHandler
|
||||
CREATE TABLE IF NOT EXISTS live_routes_v4 (
|
||||
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
|
||||
mask_length INTEGER NOT NULL, -- CIDR mask length (0-32)
|
||||
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,
|
||||
-- IPv4 range columns for fast lookups (NULL for IPv6)
|
||||
v4_ip_start INTEGER, -- Start of IPv4 range as 32-bit unsigned int
|
||||
v4_ip_end INTEGER, -- End of IPv4 range as 32-bit unsigned int
|
||||
-- IPv4 range columns for fast lookups
|
||||
ip_start INTEGER NOT NULL, -- Start of IPv4 range as 32-bit unsigned int
|
||||
ip_end INTEGER NOT NULL, -- End of IPv4 range as 32-bit unsigned int
|
||||
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);
|
||||
-- IPv6 routing table maintained by PrefixHandler
|
||||
CREATE TABLE IF NOT EXISTS live_routes_v6 (
|
||||
id TEXT PRIMARY KEY,
|
||||
prefix TEXT NOT NULL,
|
||||
mask_length INTEGER NOT NULL, -- CIDR mask length (0-128)
|
||||
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,
|
||||
-- Note: IPv6 doesn't use integer range columns
|
||||
UNIQUE(prefix, origin_asn, peer_ip)
|
||||
);
|
||||
|
||||
-- Indexes for live_routes_v4 table
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_prefix ON live_routes_v4(prefix);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_mask_length ON live_routes_v4(mask_length);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_origin_asn ON live_routes_v4(origin_asn);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_last_updated ON live_routes_v4(last_updated);
|
||||
-- Indexes for IPv4 range queries
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_ipv4_range ON live_routes(v4_ip_start, v4_ip_end) WHERE ip_version = 4;
|
||||
-- Index to optimize COUNT(DISTINCT prefix) queries
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_ip_mask_prefix ON live_routes(ip_version, mask_length, prefix);
|
||||
-- Index to optimize AS detail queries
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_origin_asn ON live_routes(origin_asn);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_ip_range ON live_routes_v4(ip_start, ip_end);
|
||||
-- Index to optimize prefix distribution queries
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v4_mask_prefix ON live_routes_v4(mask_length, prefix);
|
||||
|
||||
-- Indexes for live_routes_v6 table
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v6_prefix ON live_routes_v6(prefix);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v6_mask_length ON live_routes_v6(mask_length);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v6_origin_asn ON live_routes_v6(origin_asn);
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v6_last_updated ON live_routes_v6(last_updated);
|
||||
-- Index to optimize prefix distribution queries
|
||||
CREATE INDEX IF NOT EXISTS idx_live_routes_v6_mask_prefix ON live_routes_v6(mask_length, prefix);
|
@ -192,11 +192,20 @@ func (h *PrefixHandler) flushBatchLocked() {
|
||||
routesToUpsert = append(routesToUpsert, route)
|
||||
}
|
||||
} else if update.messageType == "withdrawal" {
|
||||
// Parse CIDR to get IP version
|
||||
_, ipVersion, err := parseCIDR(update.prefix)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to parse CIDR for withdrawal", "prefix", update.prefix, "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Create deletion record for batch delete
|
||||
routesToDelete = append(routesToDelete, database.LiveRouteDeletion{
|
||||
Prefix: update.prefix,
|
||||
OriginASN: update.originASN,
|
||||
PeerIP: update.peer,
|
||||
IPVersion: ipVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user