From 2f96141e48d7a06132baa951b14f8c15e5ebda78 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 9 Aug 2025 11:37:14 +0200 Subject: [PATCH] Fix IPv6 prefix length links to use separate /prefixlength6/ route The prefix length links for IPv6 prefixes were incorrectly pointing to /prefixlength/ which would show IPv4 prefixes. Added a new route /prefixlength6/ specifically for IPv6 prefixes and updated the template to use the correct URL based on whether displaying IPv4 or IPv6 prefix distributions. Also refactored handlePrefixLength to explicitly handle only IPv4 prefixes and created handlePrefixLength6 for IPv6 prefixes, removing the ambiguous auto-detection based on mask length value. --- internal/server/handlers.go | 118 +++++++++++++++++++++++++++++---- internal/server/routes.go | 1 + internal/templates/status.html | 6 +- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 9075ce6..2d54ead 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -20,6 +20,11 @@ import ( "github.com/go-chi/chi/v5" ) +const ( + // statsContextTimeout is the timeout for stats API operations + statsContextTimeout = 4 * time.Second +) + // handleRoot returns a handler that redirects to /status func (s *Server) handleRoot() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -81,7 +86,7 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Create a 4 second timeout context for this request - ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), statsContextTimeout) defer cancel() metrics := s.streamer.GetMetrics() @@ -215,7 +220,7 @@ func (s *Server) handleStats() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Create a 4 second timeout context for this request - ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), statsContextTimeout) defer cancel() // Check if context is already cancelled @@ -762,7 +767,7 @@ func (s *Server) handleIPRedirect() http.HandlerFunc { } } -// handlePrefixLength shows a random sample of prefixes with the specified mask length +// handlePrefixLength shows a random sample of IPv4 prefixes with the specified mask length func (s *Server) handlePrefixLength() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { lengthStr := chi.URLParam(r, "length") @@ -779,22 +784,107 @@ func (s *Server) handlePrefixLength() http.HandlerFunc { return } - // Determine IP version based on mask length - const ( - maxIPv4MaskLength = 32 - maxIPv6MaskLength = 128 - ) - var ipVersion int - if maskLength <= maxIPv4MaskLength { - ipVersion = 4 - } else if maskLength <= maxIPv6MaskLength { - ipVersion = 6 - } else { + // Validate IPv4 mask length + const maxIPv4MaskLength = 32 + if maskLength < 0 || maskLength > maxIPv4MaskLength { + http.Error(w, "Invalid IPv4 mask length", http.StatusBadRequest) + + return + } + + const ipVersion = 4 + + // Get random sample of prefixes + const maxPrefixes = 500 + prefixes, err := s.db.GetRandomPrefixesByLengthContext(r.Context(), maskLength, ipVersion, maxPrefixes) + if err != nil { + s.logger.Error("Failed to get prefixes by length", "error", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + + return + } + + // Sort prefixes for display + sort.Slice(prefixes, func(i, j int) bool { + // First compare by IP version + if prefixes[i].IPVersion != prefixes[j].IPVersion { + return prefixes[i].IPVersion < prefixes[j].IPVersion + } + // Then by prefix + return prefixes[i].Prefix < prefixes[j].Prefix + }) + + // Create enhanced prefixes with AS descriptions + type EnhancedPrefix struct { + database.LiveRoute + OriginASDescription string + Age string + } + + enhancedPrefixes := make([]EnhancedPrefix, len(prefixes)) + for i, prefix := range prefixes { + enhancedPrefixes[i] = EnhancedPrefix{ + LiveRoute: prefix, + Age: formatAge(prefix.LastUpdated), + } + + // Get AS description + if asInfo, ok := asinfo.Get(prefix.OriginASN); ok { + enhancedPrefixes[i].OriginASDescription = asInfo.Description + } + } + + // Render template + data := map[string]interface{}{ + "MaskLength": maskLength, + "IPVersion": ipVersion, + "Prefixes": enhancedPrefixes, + "Count": len(prefixes), + } + + // Check if context is still valid before writing response + select { + case <-r.Context().Done(): + // Request was cancelled, don't write response + return + default: + } + + tmpl := templates.PrefixLengthTemplate() + if err := tmpl.Execute(w, data); err != nil { + s.logger.Error("Failed to render prefix length template", "error", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + } +} + +// handlePrefixLength6 shows a random sample of IPv6 prefixes with the specified mask length +func (s *Server) handlePrefixLength6() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + lengthStr := chi.URLParam(r, "length") + if lengthStr == "" { + http.Error(w, "Length parameter is required", http.StatusBadRequest) + + return + } + + maskLength, err := strconv.Atoi(lengthStr) + if err != nil { http.Error(w, "Invalid mask length", http.StatusBadRequest) return } + // Validate IPv6 mask length + const maxIPv6MaskLength = 128 + if maskLength < 0 || maskLength > maxIPv6MaskLength { + http.Error(w, "Invalid IPv6 mask length", http.StatusBadRequest) + + return + } + + const ipVersion = 6 + // Get random sample of prefixes const maxPrefixes = 500 prefixes, err := s.db.GetRandomPrefixesByLengthContext(r.Context(), maskLength, ipVersion, maxPrefixes) diff --git a/internal/server/routes.go b/internal/server/routes.go index 1ba7356..848aeca 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -29,6 +29,7 @@ func (s *Server) setupRoutes() { r.Get("/as/{asn}", s.handleASDetail()) r.Get("/prefix/{prefix}", s.handlePrefixDetail()) r.Get("/prefixlength/{length}", s.handlePrefixLength()) + r.Get("/prefixlength6/{length}", s.handlePrefixLength6()) r.Get("/ip/{ip}", s.handleIPRedirect()) // API routes diff --git a/internal/templates/status.html b/internal/templates/status.html index 0f91747..9805db6 100644 --- a/internal/templates/status.html +++ b/internal/templates/status.html @@ -236,12 +236,16 @@ // Sort by mask length distribution.sort((a, b) => a.mask_length - b.mask_length); + // Determine the URL path based on whether this is IPv4 or IPv6 + const isIPv6 = elementId.includes('ipv6'); + const urlPath = isIPv6 ? '/prefixlength6/' : '/prefixlength/'; + distribution.forEach(item => { const metric = document.createElement('div'); metric.className = 'metric'; metric.innerHTML = ` /${item.mask_length} - ${formatNumber(item.count)} + ${formatNumber(item.count)} `; container.appendChild(metric); });