Browse Source

improve CM server bootstrap discovery

* remove builtin cm list
* add bootstrap_from_dns to resolve cm0.steampowered.com
* add error and logging when cm server list is empty
* add auto_discovery flag; true by default
* when CMServerList is empty it will try to bootstrap from webapi, with dns fallback
pull/168/head
Rossen Georgiev 7 years ago
parent
commit
470b288eb8
  1. 98
      steam/core/cm.py

98
steam/core/cm.py

@ -7,6 +7,7 @@ from collections import defaultdict
from io import BytesIO from io import BytesIO
import socket
import gevent import gevent
from random import shuffle from random import shuffle
@ -99,7 +100,7 @@ class CMClient(EventEmitter):
:param retry: number of retries before returning. Unlimited when set to ``None`` :param retry: number of retries before returning. Unlimited when set to ``None``
:type retry: :class:`int` :type retry: :class:`int`
:param delay: delay in secnds before connection attempt :param delay: delay in seconds before connection attempt
:type delay: :class:`int` :type delay: :class:`int`
:return: successful connection :return: successful connection
:rtype: :class:`bool` :rtype: :class:`bool`
@ -119,19 +120,27 @@ class CMClient(EventEmitter):
self._LOG.debug("Connect initiated.") self._LOG.debug("Connect initiated.")
if len(self.cm_servers) == 0 and not self.cm_servers.auto_discovery:
self._LOG.error("CM server list is empty.")
self._connecting = False
return False
for i, server_addr in enumerate(self.cm_servers): for i, server_addr in enumerate(self.cm_servers):
if retry and i > retry: if retry and i > retry:
self._connecting = False
return False return False
start = time() start = time()
if self.connection.connect(server_addr): if server_addr:
break if self.connection.connect(server_addr):
break
self._LOG.debug("Failed to connect. Retrying...")
else:
self._LOG.debug("No servers available. Retrying...")
diff = time() - start diff = time() - start
self._LOG.debug("Failed to connect. Retrying...")
if diff < 5: if diff < 5:
self.sleep(5 - diff) self.sleep(5 - diff)
@ -400,6 +409,7 @@ class CMServerList(object):
Good = 1 Good = 1
Bad = 2 Bad = 2
auto_discovery = True #: whether to automatically try to bootstrap CM server list
def __init__(self, bad_timespan=300): def __init__(self, bad_timespan=300):
self._LOG = logging.getLogger("CMServerList") self._LOG = logging.getLogger("CMServerList")
@ -407,7 +417,11 @@ class CMServerList(object):
self.bad_timespan = bad_timespan self.bad_timespan = bad_timespan
self.list = defaultdict(dict) self.list = defaultdict(dict)
self.bootstrap_from_builtin_list() def __len__(self):
return len(self.list)
def __repr__(self):
return "<%s: %d servers>" % (self.__class__.__name__, len(self))
def clear(self): def clear(self):
"""Clears the server list""" """Clears the server list"""
@ -415,38 +429,30 @@ class CMServerList(object):
self._LOG.debug("List cleared.") self._LOG.debug("List cleared.")
self.list.clear() self.list.clear()
def bootstrap_from_builtin_list(self): def bootstrap_from_dns(self):
""" """
Resets the server list to the built in one. Fetches CM server list from WebAPI and replaces the current one
This method is called during initialization.
""" """
self._LOG.debug("Bootstraping from builtin list") self._LOG.debug("Attempting bootstrap via DNS")
self.clear()
# build-in list try:
self.merge_list([ answer = socket.getaddrinfo("cm0.steampowered.com",
('162.254.193.7', 27018), 27017,
('208.78.164.9', 27018), socket.AF_INET,
('208.78.164.11', 27017), proto=socket.IPPROTO_TCP)
('162.254.193.7', 27019), except Exception as exp:
('162.254.193.47', 27017), self._LOG.error("DNS boostrap failed: %s" % str(exp))
('155.133.242.9', 27019), return False
('208.78.164.14', 27018),
('155.133.242.8', 27018), servers = list(map(lambda addr: addr[4], answer))
('162.254.195.45', 27017),
('208.78.164.10', 27018), if servers:
('208.78.164.12', 27017), self.clear()
('208.64.201.176', 27018), self.merge_list(servers)
('146.66.152.10', 27017), return True
('162.254.193.46', 27019), else:
('185.25.180.14', 27017), self._LOG.error("DNS boostrap: cm0.steampowered.com resolved no A records")
('162.254.193.46', 27018), return False
('155.133.242.9', 27017),
('162.254.195.44', 27018),
('162.254.195.45', 27018),
('208.78.164.9', 27017),
('208.78.164.11', 27019)
])
def bootstrap_from_webapi(self, cellid=0): def bootstrap_from_webapi(self, cellid=0):
""" """
@ -461,7 +467,8 @@ class CMServerList(object):
from steam import webapi from steam import webapi
try: try:
resp = webapi.get('ISteamDirectory', 'GetCMList', 1, params={'cellid': cellid}) resp = webapi.get('ISteamDirectory', 'GetCMList', 1, params={'cellid': cellid,
'http_timeout': 3})
except Exception as exp: except Exception as exp:
self._LOG.error("WebAPI boostrap failed: %s" % str(exp)) self._LOG.error("WebAPI boostrap failed: %s" % str(exp))
return False return False
@ -485,9 +492,22 @@ class CMServerList(object):
return True return True
def __iter__(self): def __iter__(self):
def genfunc(): def cm_server_iter():
while True: while True:
good_servers = list(filter(lambda x: x[1]['quality'] == CMServerList.Good, self.list.items())) if self.auto_discovery:
if not self.list:
self.bootstrap_from_webapi()
if not self.list:
self.bootstrap_from_dns()
if not self.list:
yield None
elif not self.list:
self._LOG.error("Server list is empty.")
return
good_servers = list(filter(lambda x: x[1]['quality'] == CMServerList.Good,
self.list.items()
))
if len(good_servers) == 0: if len(good_servers) == 0:
self.reset_all() self.reset_all()
@ -498,7 +518,7 @@ class CMServerList(object):
for server_addr, meta in good_servers: for server_addr, meta in good_servers:
yield server_addr yield server_addr
return genfunc() return cm_server_iter()
def reset_all(self): def reset_all(self):
"""Reset status for all servers in the list""" """Reset status for all servers in the list"""

Loading…
Cancel
Save