Remove BGP keepalive logging and add peer tracking
- Created bgp_peers table to track all BGP peers - Added PeerHandler to update peer last seen times for all message types - Removed verbose BGP keepalive debug logging - BGP keepalive messages now silently update peer tracking Refactor HTML templates to use go:embed - Created internal/templates package with embedded templates - Moved status.html from inline const to separate file - Templates are parsed once on startup - Server now uses parsed template instead of raw string Optimize AS data embedding with gzip compression - Changed asinfo package to embed gzipped data (2.4MB vs 12MB) - Updated Makefile to gzip AS data during update - Added decompression during initialization - Raw JSON file excluded from git
This commit is contained in:
652012
pkg/asinfo/asdata.json
652012
pkg/asinfo/asdata.json
File diff suppressed because it is too large
Load Diff
BIN
pkg/asinfo/asdata.json.gz
Normal file
BIN
pkg/asinfo/asdata.json.gz
Normal file
Binary file not shown.
@@ -2,13 +2,16 @@
|
||||
package asinfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//go:embed asdata.json
|
||||
var asDataJSON []byte
|
||||
//go:embed asdata.json.gz
|
||||
var asDataGZ []byte
|
||||
|
||||
// Info represents information about an Autonomous System
|
||||
type Info struct {
|
||||
@@ -24,8 +27,10 @@ type Registry struct {
|
||||
}
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals // Singleton pattern for embedded data
|
||||
defaultRegistry *Registry
|
||||
once sync.Once
|
||||
//nolint:gochecknoglobals // Singleton pattern for embedded data
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// initRegistry initializes the default registry with embedded data
|
||||
@@ -33,12 +38,26 @@ func initRegistry() {
|
||||
defaultRegistry = &Registry{
|
||||
byASN: make(map[int]*Info),
|
||||
}
|
||||
|
||||
|
||||
// Decompress the gzipped data
|
||||
gzReader, err := gzip.NewReader(bytes.NewReader(asDataGZ))
|
||||
if err != nil {
|
||||
panic("failed to create gzip reader: " + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
_ = gzReader.Close()
|
||||
}()
|
||||
|
||||
jsonData, err := io.ReadAll(gzReader)
|
||||
if err != nil {
|
||||
panic("failed to decompress AS data: " + err.Error())
|
||||
}
|
||||
|
||||
var asInfos []Info
|
||||
if err := json.Unmarshal(asDataJSON, &asInfos); err != nil {
|
||||
if err := json.Unmarshal(jsonData, &asInfos); err != nil {
|
||||
panic("failed to unmarshal embedded AS data: " + err.Error())
|
||||
}
|
||||
|
||||
|
||||
for i := range asInfos {
|
||||
info := &asInfos[i]
|
||||
defaultRegistry.byASN[info.ASN] = info
|
||||
@@ -48,18 +67,19 @@ func initRegistry() {
|
||||
// Get returns AS information for the given ASN
|
||||
func Get(asn int) (*Info, bool) {
|
||||
once.Do(initRegistry)
|
||||
|
||||
|
||||
defaultRegistry.mu.RLock()
|
||||
defer defaultRegistry.mu.RUnlock()
|
||||
|
||||
|
||||
info, ok := defaultRegistry.byASN[asn]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
||||
// Return a copy to prevent mutation
|
||||
copy := *info
|
||||
return ©, true
|
||||
infoCopy := *info
|
||||
|
||||
return &infoCopy, true
|
||||
}
|
||||
|
||||
// GetDescription returns just the description for an ASN
|
||||
@@ -68,6 +88,7 @@ func GetDescription(asn int) string {
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return info.Description
|
||||
}
|
||||
|
||||
@@ -77,16 +98,17 @@ func GetHandle(asn int) string {
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return info.Handle
|
||||
}
|
||||
|
||||
// Total returns the total number of AS entries in the registry
|
||||
func Total() int {
|
||||
once.Do(initRegistry)
|
||||
|
||||
|
||||
defaultRegistry.mu.RLock()
|
||||
defer defaultRegistry.mu.RUnlock()
|
||||
|
||||
|
||||
return len(defaultRegistry.byASN)
|
||||
}
|
||||
|
||||
@@ -94,15 +116,15 @@ func Total() int {
|
||||
// Note: This creates a copy of all data, use sparingly
|
||||
func All() []Info {
|
||||
once.Do(initRegistry)
|
||||
|
||||
|
||||
defaultRegistry.mu.RLock()
|
||||
defer defaultRegistry.mu.RUnlock()
|
||||
|
||||
|
||||
result := make([]Info, 0, len(defaultRegistry.byASN))
|
||||
for _, info := range defaultRegistry.byASN {
|
||||
result = append(result, *info)
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -110,17 +132,17 @@ func All() []Info {
|
||||
// This is a simple case-sensitive substring search
|
||||
func Search(query string) []Info {
|
||||
once.Do(initRegistry)
|
||||
|
||||
|
||||
defaultRegistry.mu.RLock()
|
||||
defer defaultRegistry.mu.RUnlock()
|
||||
|
||||
|
||||
var results []Info
|
||||
for _, info := range defaultRegistry.byASN {
|
||||
if contains(info.Handle, query) || contains(info.Description, query) {
|
||||
results = append(results, *info)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -135,5 +157,6 @@ func containsImpl(s, substr string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestGet(t *testing.T) {
|
||||
t.Errorf("Get(%d) ok = %v, want %v", tt.asn, ok, tt.wantOK)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if ok && info.Description != tt.wantDesc {
|
||||
t.Errorf("Get(%d) description = %q, want %q", tt.asn, info.Description, tt.wantDesc)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func TestTotal(t *testing.T) {
|
||||
if total < 100000 {
|
||||
t.Errorf("Total() = %d, expected > 100000", total)
|
||||
}
|
||||
|
||||
|
||||
// Verify it's consistent
|
||||
if total2 := Total(); total2 != total {
|
||||
t.Errorf("Total() returned different values: %d vs %d", total, total2)
|
||||
@@ -134,7 +134,7 @@ func TestSearch(t *testing.T) {
|
||||
if len(results) < tt.wantMin {
|
||||
t.Errorf("Search(%q) returned %d results, want at least %d", tt.query, len(results), tt.wantMin)
|
||||
}
|
||||
|
||||
|
||||
// Verify all results contain the query
|
||||
if tt.query != "" {
|
||||
for _, r := range results {
|
||||
@@ -157,7 +157,7 @@ func TestDataIntegrity(t *testing.T) {
|
||||
}
|
||||
seen[info.ASN] = true
|
||||
}
|
||||
|
||||
|
||||
// Verify all entries have required fields
|
||||
for _, info := range all {
|
||||
if info.Handle == "" && info.ASN != 0 {
|
||||
@@ -172,7 +172,7 @@ func TestDataIntegrity(t *testing.T) {
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
// Common ASNs to lookup
|
||||
asns := []int{1, 15169, 13335, 32934, 8075, 16509}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Get(asns[i%len(asns)])
|
||||
@@ -181,9 +181,9 @@ func BenchmarkGet(b *testing.B) {
|
||||
|
||||
func BenchmarkSearch(b *testing.B) {
|
||||
queries := []string{"Google", "Amazon", "Microsoft", "University"}
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Search(queries[i%len(queries)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,4 @@ Basic usage:
|
||||
The data is loaded lazily on first access and cached in memory for the lifetime
|
||||
of the program. All getter methods are safe for concurrent use.
|
||||
*/
|
||||
package asinfo
|
||||
package asinfo
|
||||
|
||||
@@ -26,4 +26,4 @@ func ExampleSearch() {
|
||||
fmt.Printf("AS%d: %s - %s\n", info.ASN, info.Handle, info.Description)
|
||||
}
|
||||
// Output: AS3: MIT-GATEWAYS - Massachusetts Institute of Technology
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user