Fix prefix URL routing to handle CIDR notation with slashes
- Use wildcard route pattern for /prefix/* endpoints - Extract prefix parameter using chi.URLParam(r, "*") - Fixes 400 error when accessing /prefix/x.x.x.x/32 directly
This commit is contained in:
parent
9043cf9bc0
commit
1115954827
@ -30,6 +30,14 @@ type Tracker struct {
|
|||||||
// Route update metrics
|
// Route update metrics
|
||||||
ipv4UpdateRate metrics.Meter
|
ipv4UpdateRate metrics.Meter
|
||||||
ipv6UpdateRate metrics.Meter
|
ipv6UpdateRate metrics.Meter
|
||||||
|
|
||||||
|
// Announcement/withdrawal metrics
|
||||||
|
announcementCounter metrics.Counter
|
||||||
|
withdrawalCounter metrics.Counter
|
||||||
|
churnRate metrics.Meter // combined announcements + withdrawals per second
|
||||||
|
|
||||||
|
// BGP peer tracking
|
||||||
|
bgpPeerCount atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new metrics tracker
|
// New creates a new metrics tracker
|
||||||
@ -46,6 +54,9 @@ func New() *Tracker {
|
|||||||
wireByteRate: metrics.NewMeter(),
|
wireByteRate: metrics.NewMeter(),
|
||||||
ipv4UpdateRate: metrics.NewMeter(),
|
ipv4UpdateRate: metrics.NewMeter(),
|
||||||
ipv6UpdateRate: metrics.NewMeter(),
|
ipv6UpdateRate: metrics.NewMeter(),
|
||||||
|
announcementCounter: metrics.NewCounter(),
|
||||||
|
withdrawalCounter: metrics.NewCounter(),
|
||||||
|
churnRate: metrics.NewMeter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +145,56 @@ func (t *Tracker) RecordIPv6Update() {
|
|||||||
t.ipv6UpdateRate.Mark(1)
|
t.ipv6UpdateRate.Mark(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecordAnnouncement records a route announcement
|
||||||
|
func (t *Tracker) RecordAnnouncement() {
|
||||||
|
t.announcementCounter.Inc(1)
|
||||||
|
t.churnRate.Mark(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordWithdrawal records a route withdrawal
|
||||||
|
func (t *Tracker) RecordWithdrawal() {
|
||||||
|
t.withdrawalCounter.Inc(1)
|
||||||
|
t.churnRate.Mark(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBGPPeerCount updates the current BGP peer count
|
||||||
|
func (t *Tracker) SetBGPPeerCount(count int) {
|
||||||
|
// BGP peer count is always small (< 1000), so int32 is safe
|
||||||
|
if count > 0 && count < 1<<31 {
|
||||||
|
t.bgpPeerCount.Store(int32(count)) //nolint:gosec // count is validated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBGPPeerCount returns the current BGP peer count
|
||||||
|
func (t *Tracker) GetBGPPeerCount() int {
|
||||||
|
return int(t.bgpPeerCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnouncementCount returns the total announcement count
|
||||||
|
func (t *Tracker) GetAnnouncementCount() uint64 {
|
||||||
|
count := t.announcementCounter.Count()
|
||||||
|
if count < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWithdrawalCount returns the total withdrawal count
|
||||||
|
func (t *Tracker) GetWithdrawalCount() uint64 {
|
||||||
|
count := t.withdrawalCounter.Count()
|
||||||
|
if count < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChurnRate returns the route churn rate per second
|
||||||
|
func (t *Tracker) GetChurnRate() float64 {
|
||||||
|
return t.churnRate.Rate1()
|
||||||
|
}
|
||||||
|
|
||||||
// GetRouteMetrics returns current route update metrics
|
// GetRouteMetrics returns current route update metrics
|
||||||
func (t *Tracker) GetRouteMetrics() RouteMetrics {
|
func (t *Tracker) GetRouteMetrics() RouteMetrics {
|
||||||
return RouteMetrics{
|
return RouteMetrics{
|
||||||
|
|||||||
@ -320,6 +320,23 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GCStats represents garbage collection statistics
|
||||||
|
type GCStats struct {
|
||||||
|
NumGC uint32 `json:"num_gc"`
|
||||||
|
TotalPauseMs uint64 `json:"total_pause_ms"`
|
||||||
|
LastPauseMs float64 `json:"last_pause_ms"`
|
||||||
|
HeapAllocBytes uint64 `json:"heap_alloc_bytes"`
|
||||||
|
HeapSysBytes uint64 `json:"heap_sys_bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamStats represents stream statistics including announcements/withdrawals
|
||||||
|
type StreamStats struct {
|
||||||
|
Announcements uint64 `json:"announcements"`
|
||||||
|
Withdrawals uint64 `json:"withdrawals"`
|
||||||
|
RouteChurnPerSec float64 `json:"route_churn_per_sec"`
|
||||||
|
BGPPeerCount int `json:"bgp_peer_count"`
|
||||||
|
}
|
||||||
|
|
||||||
// StatsResponse represents the API statistics response
|
// StatsResponse represents the API statistics response
|
||||||
type StatsResponse struct {
|
type StatsResponse struct {
|
||||||
Uptime string `json:"uptime"`
|
Uptime string `json:"uptime"`
|
||||||
@ -335,6 +352,8 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
GoVersion string `json:"go_version"`
|
GoVersion string `json:"go_version"`
|
||||||
Goroutines int `json:"goroutines"`
|
Goroutines int `json:"goroutines"`
|
||||||
MemoryUsage string `json:"memory_usage"`
|
MemoryUsage string `json:"memory_usage"`
|
||||||
|
GC GCStats `json:"gc"`
|
||||||
|
Stream StreamStats `json:"stream"`
|
||||||
ASNs int `json:"asns"`
|
ASNs int `json:"asns"`
|
||||||
Prefixes int `json:"prefixes"`
|
Prefixes int `json:"prefixes"`
|
||||||
IPv4Prefixes int `json:"ipv4_prefixes"`
|
IPv4Prefixes int `json:"ipv4_prefixes"`
|
||||||
@ -685,7 +704,8 @@ func (s *Server) handleASDetailJSON() http.HandlerFunc {
|
|||||||
// handlePrefixDetailJSON returns prefix details as JSON
|
// handlePrefixDetailJSON returns prefix details as JSON
|
||||||
func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
prefixParam := chi.URLParam(r, "prefix")
|
// Get wildcard parameter (everything after /prefix/)
|
||||||
|
prefixParam := chi.URLParam(r, "*")
|
||||||
if prefixParam == "" {
|
if prefixParam == "" {
|
||||||
writeJSONError(w, http.StatusBadRequest, "Prefix parameter is required")
|
writeJSONError(w, http.StatusBadRequest, "Prefix parameter is required")
|
||||||
|
|
||||||
@ -851,7 +871,8 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
|||||||
// handlePrefixDetail returns a handler that serves the prefix detail HTML page
|
// handlePrefixDetail returns a handler that serves the prefix detail HTML page
|
||||||
func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
prefixParam := chi.URLParam(r, "prefix")
|
// Get wildcard parameter (everything after /prefix/)
|
||||||
|
prefixParam := chi.URLParam(r, "*")
|
||||||
if prefixParam == "" {
|
if prefixParam == "" {
|
||||||
http.Error(w, "Prefix parameter is required", http.StatusBadRequest)
|
http.Error(w, "Prefix parameter is required", http.StatusBadRequest)
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ func (s *Server) setupRoutes() {
|
|||||||
|
|
||||||
// AS and prefix detail pages
|
// AS and prefix detail pages
|
||||||
r.Get("/as/{asn}", s.handleASDetail())
|
r.Get("/as/{asn}", s.handleASDetail())
|
||||||
r.Get("/prefix/{prefix}", s.handlePrefixDetail())
|
r.Get("/prefix/*", s.handlePrefixDetail())
|
||||||
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
||||||
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func (s *Server) setupRoutes() {
|
|||||||
r.Get("/stats", s.handleStats())
|
r.Get("/stats", s.handleStats())
|
||||||
r.Get("/ip/{ip}", s.handleIPLookup())
|
r.Get("/ip/{ip}", s.handleIPLookup())
|
||||||
r.Get("/as/{asn}", s.handleASDetailJSON())
|
r.Get("/as/{asn}", s.handleASDetailJSON())
|
||||||
r.Get("/prefix/{prefix}", s.handlePrefixDetailJSON())
|
r.Get("/prefix/*", s.handlePrefixDetailJSON())
|
||||||
})
|
})
|
||||||
|
|
||||||
s.router = r
|
s.router = r
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user