diff --git a/config.go b/config.go index aac7476..5c5792e 100644 --- a/config.go +++ b/config.go @@ -47,6 +47,9 @@ type Config struct { // CheckURL is the url used to verify mirror versions CheckURL string `mapstructure:"checkUrl"` + // SameCityThreshold is the parameter used to specify a threshold between mirrors and the client + SameCityThreshold float64 `mapstructure:"sameCityThreshold"` + // ServerList is a list of ServerConfig structs, which gets parsed into servers. ServerList []ServerConfig `mapstructure:"servers"` @@ -160,6 +163,11 @@ func (r *Redirector) ReloadConfig() error { r.config.TopChoices = len(r.servers) } + // Check if on the config is declared or use default logic + if r.config.SameCityThreshold == 0 { + r.config.SameCityThreshold = 200000.0 + } + // Force check go r.servers.Check(r, r.checks) diff --git a/dlrouter.yaml b/dlrouter.yaml index 3446ebf..8330145 100644 --- a/dlrouter.yaml +++ b/dlrouter.yaml @@ -3,6 +3,8 @@ geodb: GeoLite2-City.mmdb asndb: GeoLite2-ASN.mmdb dl_map: userdata.csv +sameCityThreshold: 200000.0 + checkUrl: https://imola.armbian.com/apt/.control # LRU Cache Size (in items) diff --git a/servers.go b/servers.go index 6058063..a9e40d4 100644 --- a/servers.go +++ b/servers.go @@ -196,122 +196,134 @@ type ComputedDistance struct { // 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) { - cacheKey := scheme + "_" + ip.String() + cacheKey := scheme + "_" + ip.String() - if cached, exists := r.serverCache.Get(cacheKey); exists { - if comp, ok := cached.(ComputedDistance); ok { - log.Infof("Cache hit: %s", comp.Server.Host) - return comp.Server, comp.Distance, nil - } - r.serverCache.Remove(cacheKey) - } + if cached, exists := r.serverCache.Get(cacheKey); exists { + if comp, ok := cached.(ComputedDistance); ok { + log.Infof("Cache hit: %s", comp.Server.Host) + return comp.Server, comp.Distance, nil + } + r.serverCache.Remove(cacheKey) + } - var city db.City - if err := r.db.Lookup(ip, &city); err != nil { - log.WithError(err).Warning("Unable to lookup client location") - return nil, -1, err - } - clientCountry := city.Country.IsoCode - var asn db.ASN - if r.asnDB != nil { - if err := r.asnDB.Lookup(ip, &asn); err != nil { - log.WithError(err).Warning("Unable to load ASN information") - return nil, -1, err - } - } + var city db.City + if err := r.db.Lookup(ip, &city); err != nil { + log.WithError(err).Warning("Unable to lookup client location") + return nil, -1, err + } + clientCountry := city.Country.IsoCode - ruleInput := RuleInput{ - IP: ip.String(), - ASN: asn, - Location: city, - } + var asn db.ASN + if r.asnDB != nil { + if err := r.asnDB.Lookup(ip, &asn); err != nil { + log.WithError(err).Warning("Unable to load ASN information") + return nil, -1, err + } + } - validServers := lo.Filter(s, func(server *Server, _ int) bool { - if !server.Available || !lo.Contains(server.Protocols, scheme) { - return false - } - if len(server.Rules) > 0 && !server.checkRules(ruleInput) { - log.WithField("host", server.Host).Debug("Skipping server due to rules") - return false - } - return true - }) - - if len(validServers) < 2 { - validServers = s - } - localServers := lo.Filter(validServers, func(server *Server, _ int) bool { - return server.Country == clientCountry - }) + ruleInput := RuleInput{ + IP: ip.String(), + ASN: asn, + Location: city, + } - const sameCityThreshold = 20000000.0 // 200 km + validServers := lo.Filter(s, func(server *Server, _ int) bool { + if !server.Available || !lo.Contains(server.Protocols, scheme) { + return false + } + if len(server.Rules) > 0 && !server.checkRules(ruleInput) { + log.WithField("host", server.Host).Debug("Skipping server due to rules") + return false + } + return true + }) + + if len(validServers) < 2 { + validServers = s + } + + localServers := lo.Filter(validServers, func(server *Server, _ int) bool { + return server.Country == clientCountry + }) + + if len(localServers) > 0 { + computedLocal := lo.Map(localServers, func(server *Server, _ int) ComputedDistance { + d := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude) + return ComputedDistance{ + Server: server, + Distance: d, + } + }) + + sort.Slice(computedLocal, func(i, j int) bool { + return computedLocal[i].Distance < computedLocal[j].Distance + }) + + if computedLocal[0].Distance < r.config.SameCityThreshold { + chosen := computedLocal[0] + r.serverCache.Add(cacheKey, chosen) + return chosen.Server, chosen.Distance, nil + } + + choiceCount := r.config.TopChoices + if len(computedLocal) < choiceCount { + choiceCount = len(computedLocal) + } + + choices := make([]randutil.Choice, choiceCount) + for i, item := range computedLocal[:choiceCount] { + choices[i] = randutil.Choice{ + Weight: item.Server.Weight, + Item: item, + } + } + + choice, err := randutil.WeightedChoice(choices) + if err != nil { + log.WithError(err).Warning("Unable to choose a weighted choice") + return nil, -1, err + } + + dist := choice.Item.(ComputedDistance) + r.serverCache.Add(cacheKey, dist) + return dist.Server, dist.Distance, nil + } - if len(localServers) > 0 { - computedLocal := lo.Map(localServers, func(server *Server, _ int) ComputedDistance { - d := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude) - return ComputedDistance{ - Server: server, - Distance: d, - } - }) - sort.Slice(computedLocal, func(i, j int) bool { - return computedLocal[i].Distance < computedLocal[j].Distance - }) - if computedLocal[0].Distance < sameCityThreshold { - chosen := computedLocal[0] - r.serverCache.Add(cacheKey, chosen) - return chosen.Server, chosen.Distance, nil - } - choiceCount := r.config.TopChoices - if len(computedLocal) < choiceCount { - choiceCount = len(computedLocal) - } - choices := make([]randutil.Choice, choiceCount) - for i, item := range computedLocal[0:choiceCount] { - choices[i] = randutil.Choice{ - Weight: item.Server.Weight, - Item: item, - } - } - r.serverCache.Add(cacheKey, choices) - choice, err := randutil.WeightedChoice(choices) - if err != nil { - log.WithError(err).Warning("Unable to choose a weighted choice") - return nil, -1, err - } - dist := choice.Item.(ComputedDistance) - return dist.Server, dist.Distance, nil - } // Fallback: if no local servers exist, simply select the nearest server among all valid servers. - computed := lo.Map(validServers, func(server *Server, _ int) ComputedDistance { - d := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude) - return ComputedDistance{ - Server: server, - Distance: d, - } - }) - sort.Slice(computed, func(i, j int) bool { - return computed[i].Distance < computed[j].Distance - }) - choiceCount := r.config.TopChoices - if len(computed) < choiceCount { - choiceCount = len(computed) - } - choices := make([]randutil.Choice, choiceCount) - for i, item := range computed[0:choiceCount] { - choices[i] = randutil.Choice{ - Weight: item.Server.Weight, - Item: item, - } - } - r.serverCache.Add(cacheKey, choices) - choice, err := randutil.WeightedChoice(choices) - if err != nil { - log.WithError(err).Warning("Unable to choose a weighted choice") - return nil, -1, err - } - dist := choice.Item.(ComputedDistance) - return dist.Server, dist.Distance, nil + computed := lo.Map(validServers, func(server *Server, _ int) ComputedDistance { + d := Distance(city.Location.Latitude, city.Location.Longitude, server.Latitude, server.Longitude) + return ComputedDistance{ + Server: server, + Distance: d, + } + }) + + sort.Slice(computed, func(i, j int) bool { + return computed[i].Distance < computed[j].Distance + }) + + choiceCount := r.config.TopChoices + if len(computed) < choiceCount { + choiceCount = len(computed) + } + + choices := make([]randutil.Choice, choiceCount) + for i, item := range computed[:choiceCount] { + choices[i] = randutil.Choice{ + Weight: item.Server.Weight, + Item: item, + } + } + + choice, err := randutil.WeightedChoice(choices) + if err != nil { + log.WithError(err).Warning("Unable to choose a weighted choice") + return nil, -1, err + } + + dist := choice.Item.(ComputedDistance) + r.serverCache.Add(cacheKey, dist) + return dist.Server, dist.Distance, nil } // haversin(θ) function