From 0edff1557b68a5b95b54d3c03fb4e2197ce015ac Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Tue, 6 Jan 2026 02:10:19 +0100 Subject: [PATCH] filter servers using ipv6 --- check.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ config.go | 11 +++++++++++ http.go | 5 ++++- redirector.go | 3 +++ servers.go | 13 ++++++++++++- 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/check.go b/check.go index 0fb928c..e6fd900 100644 --- a/check.go +++ b/check.go @@ -232,6 +232,53 @@ func (t *TLSCheck) Check(server *Server, logFields log.Fields) (bool, error) { return true, nil } +// IPv6Check verifies that a server has a valid IPv6 address by checking for AAAA records. +type IPv6Check struct { + config *Config +} + +// Check verifies IPv6 support for the server by checking for AAAA records +func (i *IPv6Check) Check(server *Server, logFields log.Fields) (bool, error) { + // Extract host from server (handle host:port format) + host := server.Host + if strings.Contains(host, ":") { + var err error + host, _, err = net.SplitHostPort(server.Host) + if err != nil { + host = server.Host + } + } + + ips, err := net.LookupIP(host) + if err != nil { + logFields["error"] = err + return true, nil // DNS lookup failure shouldn't fail the whole check + } + + // Check if any resolved IP is IPv6 + hasIPv6 := false + for _, ip := range ips { + if ip.To4() == nil && ip.To16() != nil { + hasIPv6 = true + break + } + } + + server.mu.Lock() + server.IPv6 = hasIPv6 + server.mu.Unlock() + + if hasIPv6 { + log.WithField("host", server.Host).Debug("Server has IPv6 support") + } else { + logFields["cause"] = "No AAAA record found" + log.WithField("host", server.Host).Debug("Server does not have IPv6 support") + } + + // This check doesn't fail servers, it just updates their IPv6 status + return true, nil +} + type VersionCheck struct { config *Config VersionURL string diff --git a/config.go b/config.go index a6e410d..371a356 100644 --- a/config.go +++ b/config.go @@ -337,6 +337,17 @@ func (r *Redirector) addServer(server ServerConfig, u *url.URL) (*Server, error) }).Warning("Could not resolve address") return nil, err } + + // Check for IPv6 support using resolved IPs + hasIPv6 := false + for _, ip := range ips { + if ip.To4() == nil && ip.To16() != nil { + hasIPv6 = true + break + } + } + s.IPv6 = hasIPv6 + var city db.City err = r.db.Lookup(ips[0], &city) if err != nil { diff --git a/http.go b/http.go index 962c00f..330a7de 100644 --- a/http.go +++ b/http.go @@ -96,9 +96,12 @@ func (r *Redirector) redirectHandler(w http.ResponseWriter, req *http.Request) { scheme = "http" } + // Detect if user is connecting via IPv6 + isIPv6 := ip.To4() == nil && ip.To16() != nil + // If none of the above exceptions are matched, we use the geographical distance based on IP if server == nil { - server, distance, err = r.servers.Closest(r, scheme, ip) + server, distance, err = r.servers.Closest(r, scheme, ip, isIPv6) if err != nil { log.WithError(err).Warning("Unable to find closest server") diff --git a/redirector.go b/redirector.go index dcf20a3..7c1fb10 100644 --- a/redirector.go +++ b/redirector.go @@ -77,6 +77,9 @@ func New(config *Config) *Redirector { &TLSCheck{ config: config, }, + &IPv6Check{ + config: config, + }, } if config.CheckURL != "" { diff --git a/servers.go b/servers.go index bcec2fd..2ba989a 100644 --- a/servers.go +++ b/servers.go @@ -31,6 +31,7 @@ type Server struct { Continent string `json:"continent"` Country string `json:"country"` Protocols []string `json:"protocols"` + IPv6 bool `json:"ipv6"` Rules []Rule `json:"rules,omitempty"` Redirects prometheus.Counter `json:"-"` LastChange time.Time `json:"lastChange"` @@ -201,8 +202,12 @@ type ComputedDistance struct { // it computes the distances. If the nearest server is within a threshold (e.g. 50km), // it is selected deterministically; otherwise, a weighted selection is used. // If no local servers exist, it falls back to a weighted selection among all valid servers. -func (s ServerList) Closest(r *Redirector, scheme string, ip net.IP) (*Server, float64, error) { +// If requireIPv6 is true, servers without IPv6 support are filtered out. +func (s ServerList) Closest(r *Redirector, scheme string, ip net.IP, requireIPv6 bool) (*Server, float64, error) { cacheKey := scheme + "_" + ip.String() + if requireIPv6 { + cacheKey += "_v6" + } if cached, exists := r.serverCache.Get(cacheKey); exists { if comp, ok := cached.(ComputedDistance); ok { @@ -237,6 +242,12 @@ func (s ServerList) Closest(r *Redirector, scheme string, ip net.IP) (*Server, f if !server.Available || !lo.Contains(server.Protocols, scheme) { return false } + + // If user is on IPv6, filter out servers that don't support IPv6 + if requireIPv6 && !server.IPv6 { + log.WithField("host", server.Host).Debug("Skipping server due to no IPv6 support") + return false + } if len(server.Rules) > 0 && !server.checkRules(ruleInput) { log.WithField("host", server.Host).Debug("Skipping server due to rules") return false