Fix prefix distribution bug and add prefix length pages

- Fix GetPrefixDistribution to count unique prefixes using COUNT(DISTINCT prefix) instead of COUNT(*)
- Add /prefixlength/<length> route showing random sample of 500 prefixes
- Make prefix counts on status page clickable links to prefix length pages
- Add GetRandomPrefixesByLength database method
- Create prefix_length.html template with sortable table
- Show prefix age and origin AS with descriptions
This commit is contained in:
2025-07-28 18:42:38 +02:00
parent 1dcde74a90
commit ba13c76c53
8 changed files with 562 additions and 185276 deletions

View File

@@ -884,11 +884,11 @@ func (d *Database) DeleteLiveRoute(prefix string, originASN int, peerIP string)
return err
}
// GetPrefixDistribution returns the distribution of prefixes by mask length
// GetPrefixDistribution returns the distribution of unique prefixes by mask length
func (d *Database) GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error) {
// IPv4 distribution
// IPv4 distribution - count unique prefixes, not routes
query := `
SELECT mask_length, COUNT(*) as count
SELECT mask_length, COUNT(DISTINCT prefix) as count
FROM live_routes
WHERE ip_version = 4
GROUP BY mask_length
@@ -908,9 +908,9 @@ func (d *Database) GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []Pr
ipv4 = append(ipv4, dist)
}
// IPv6 distribution
// IPv6 distribution - count unique prefixes, not routes
query = `
SELECT mask_length, COUNT(*) as count
SELECT mask_length, COUNT(DISTINCT prefix) as count
FROM live_routes
WHERE ip_version = 6
GROUP BY mask_length
@@ -1227,3 +1227,51 @@ func (d *Database) GetPrefixDetails(prefix string) ([]LiveRoute, error) {
return routes, nil
}
// GetRandomPrefixesByLength returns a random sample of prefixes with the specified mask length
func (d *Database) GetRandomPrefixesByLength(maskLength, ipVersion, limit int) ([]LiveRoute, error) {
query := `
SELECT DISTINCT
prefix, mask_length, ip_version, origin_asn, as_path,
peer_ip, last_updated
FROM live_routes
WHERE mask_length = ? AND ip_version = ?
ORDER BY RANDOM()
LIMIT ?
`
rows, err := d.db.Query(query, maskLength, ipVersion, limit)
if err != nil {
return nil, fmt.Errorf("failed to query random prefixes: %w", err)
}
defer func() {
_ = rows.Close()
}()
var routes []LiveRoute
for rows.Next() {
var route LiveRoute
var pathJSON string
err := rows.Scan(
&route.Prefix,
&route.MaskLength,
&route.IPVersion,
&route.OriginASN,
&pathJSON,
&route.PeerIP,
&route.LastUpdated,
)
if err != nil {
continue
}
// Decode AS path
if err := json.Unmarshal([]byte(pathJSON), &route.ASPath); err != nil {
route.ASPath = []int{}
}
routes = append(routes, route)
}
return routes, nil
}

View File

@@ -54,6 +54,7 @@ type Store interface {
// AS and prefix detail operations
GetASDetails(asn int) (*ASN, []LiveRoute, error)
GetPrefixDetails(prefix string) ([]LiveRoute, error)
GetRandomPrefixesByLength(maskLength, ipVersion, limit int) ([]LiveRoute, error)
// Lifecycle
Close() error