import asyncio
import datetime
import functools
+import ipaddress
import logging
import os.path
import random
Returns all mirrors in random order with preferred mirrors first
"""
# Lookup the client
- network = self.location.lookup(address)
+ network = self.location.lookup("%s" % address)
def __sort(mirror):
# Generate some random value for each mirror
r = random.random()
- # Put preferred mirrors first
- if network and mirror.is_preferred_for_network(network):
- r += 1
+ # Add the preference score
+ if network:
+ r += mirror.is_preferred_for_network(network)
return r
# Fetch all mirrors and shuffle them, but put preferred mirrors first
- return sorted([mirror async for mirror in self], key=__sort)
+ return sorted([mirror async for mirror in self], key=__sort, reverse=True)
@functools.cached_property
def location(self):
return False
+ # Addresses
+
+ @property
+ def addresses(self):
+ """
+ All addresses of the mirror, regardless of family
+ """
+ return self.addresses_ipv6 + self.addresses_ipv4
+
async def _update_country_code_and_asn(self):
"""
Updates the country code of this mirror
]
# Lookup the country code and ASN
- for address in self.addresses_ipv6 + self.addresses_ipv4:
+ for address in self.addresses:
network = self.backend.mirrors.location.lookup(address)
# Try the next IP address if we didn't find any data
break
def is_preferred_for_address(self, address):
+ """
+ Check if this mirror is preferred for this address
+ """
# Lookup the client
network = self.backend.mirrors.location.lookup("%s" % address)
+ # Check for the entire network
return self.is_preferred_for_network(network)
def is_preferred_for_network(self, network):
"""
- Returns True if this mirror is preferred for clients on the given network.
+ Returns a score of how much the mirror is preferred for this network.
"""
+ first_address = ipaddress.ip_address(network.first_address)
+ last_address = ipaddress.ip_address(network.last_address)
+
+ # Check if the mirror is on the same network
+ for address in self.addresses:
+ # Skip incompatible families
+ if isinstance(address, ipaddress.IPv6Address):
+ if not network.family == socket.AF_INET6:
+ continue
+ elif isinstance(address, ipaddress.IPv4Address):
+ if not network.family == socket.AF_INET:
+ continue
+
+ # Check if the address is within the network
+ if first_address <= address <= last_address:
+ return 4
+
# If the AS matches, we will prefer this
if self.asn and self.asn == network.asn:
- return True
+ return 3
# If the mirror and client are in the same country, we prefer this
if self.country_code and self.country_code == network.country_code:
- return True
+ return 2
- return False
+ # Check if we are on the same continent
+ if self._continent_match(self.country_code, network.country_code):
+ return 1
+
+ return 0
+
+ def _continent_match(self, cc1, cc2):
+ """
+ Checks if the two given country codes are on the same continent
+ """
+ country1 = self.backend.mirrors.location.get_country(cc1)
+ country2 = self.backend.mirrors.location.get_country(cc2)
+
+ # If we are missing either country, we don't know
+ if not country1 or not country2:
+ return False
+
+ # Return True if both countries are on the same continent
+ return country1.continent_code == country2.continent_code
# Check