Commit 12659053 authored by m!nus's avatar m!nus

Working version without packet queueing, thus increased pings

parents
#!/usr/bin/python
from __future__ import print_function
import sys
import socket
import time
from random import randint
from struct import unpack
import select
# fuck you i need UTF-8
reload(sys)
sys.setdefaultencoding('utf8')
def log(level, str):
if level is 'debug': return
print("[{0: <5}] {1}".format(level, str), file=sys.stderr)
class MultiSocket(object):
READ = 1
WRITE = 2
EXCEPTION = 4
def __init__(self, timeout=None):
self.sockets = {}
self.families = {}
self.timeout = timeout
def get(self, family):
if not self.sockets.has_key(family):
self.sockets[family] = socket.socket(family, socket.SOCK_DGRAM)
fileno = self.sockets[family].fileno()
self.families[fileno] = family
#self.sockets[family].settimeout(self.timeout)
return self.sockets[family]
def getfamily(self, socket):
fileno = socket.fileno()
if fileno in self.families:
return self.families[fileno]
else:
raise Exception('No family found for socket')
def select(self, type=None):
list_r = self.sockets.values() if (type&self.READ or type==None) else []
list_w = self.sockets.values() if type&self.WRITE else []
list_x = self.sockets.values() if type&self.EXCEPTION else []
return select.select(list_r, list_w, list_x, self.timeout)
def sendto(self, data, address):
self.get(address[0]).sendto(data, address[1])
def recvfrom(self, len=1492):
try:
(r, w, x) = select.select([self.sockets], [], [], self.timeout)
if not r and not w and not x:
return None
for sock in r:
(data, address) = sock.recvfrom(len)
return (data, (self.sockets.getfamily(sock), address))
except socket.timeout:
return None
except socket.error as e:
# Errno 10054 happens when we get ICMP port unreachable, we don't care about that
if e.errno != 10054:
raise
class Handler(object):
def match(self, **kwargs):
for name, value in kwargs.iteritems():
if hasattr(self, name) and getattr(self, name) != value:
return False
return True
def call(self, address, data):
pass
class HandlerStorage(object):
def __init__(self):
self.handlers = []
def add(self, handler):
if isinstance(handler, list):
self.handlers += handler
else:
self.handlers.append(handler)
def remove(self, handler):
if isinstance(handler, list):
for item in handler:
self.handlers.remove(item)
else:
self.handlers.remove(handler)
def find(self, **kwargs):
return [handler for handler in self.handlers if handler.match(**kwargs)]
def __repr__(self):
return str(self.handlers)
class MasterServer(Handler):
_packet_count_request = 10*b'\xff' + b'cou2'
_packet_count_response = 10*b'\xff' + b'siz2'
_packet_list_request = 10*b'\xff' + b'req2'
_packet_list_response = 10*b'\xff' + b'lis2'
_serveraddr_size = 18
def __init__(self, parent, address, name='none given'):
self.address = address
self._parent = parent
#self.data = self._packet_list_response
self.name = name
self.latency = -1
self.serverlist = ServerList()
self.server_count = -1
def request(self):
self.request_time = time.time()
self._parent.sockets.get(self.address[0]).sendto(10 * b'\xff' + b'req2', self.address[1])
self.server_count = 0
def add_from_serverlist(self, data):
if len(data) % self._serveraddr_size != 0:
raise Exception("Address packet's size not multiple of the server " + \
"address struct's size: {datalen}%{addrsize}={modulo} data={data}" \
.format(datalen=len(data), addrsize=self._serveraddr_size, \
modulo=(len(data)%self._serveraddr_size), \
data=' '.join([ "{0:2x}".format(ord(x)) for x in data ])))
for i in xrange(0, len(data), self._serveraddr_size):
if data[0:12] == b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff": # ::ffff:... == IPv4
serverAddress = (socket.AF_INET, (socket.inet_ntoa(data[i+12:i+16]), unpack("!H", data[i+16:i+18])[0]))
else:
# TODO: workaround for windows as inet_ntop doesn't exist there
if sys.platform == "win32":
log('warning', "Can't make IPv6 address on windows from binary: {0!r}".format(data[i:i+16]))
continue
address_family = socket.AF_INET6
serverAddress = (address_family (socket.inet_ntop(address_family, data[i:i+16]), unpack("!H", data[i+16:i+18])[0]))
server = Server(self._parent, serverAddress, master=self)
server.request()
self._parent.serverlist.add(server)
self.serverlist.add(server)
def call(self, address, data):
count_header_len = len(self._packet_count_response)
if data[0:count_header_len] == self._packet_count_response:
self.latency = time.time() - self.request_time
self.server_count += unpack('!H', data[count_header_len:count_header_len+2])[0]
self.add_from_serverlist(data[len(self._packet_list_response):])
def match(self, **kwargs):
if not kwargs.has_key("address") or kwargs["address"] != self.address:
return False
if not kwargs.has_key("data") or kwargs["data"][0:len(self._packet_list_response)] != self._packet_list_response:
return False
return True
def __repr__(self):
address = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
return "<MasterServer name='{name}' address='{address}' servers='{servers}'>" \
.format(name=self.name, address=address, servers=self.server_count)
class Server(Handler):
_packet_request = 10*b'\xff' + b'gie3'
_packet_response = 10*b'\xff' + b'inf3'
def __init__(self, parent, address, master=None):
self.address = address
self._parent = parent
self.data = None
self.master = master
self.latency = -1
self.playerlist = PlayerList()
self.version = None
self.name = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
self.map = None
self.gametype = None
self.password = None
self.players = -1
self.max_players = -1
self.clients = -1
self.max_clients = -1
def request(self):
#log('debug', "Server-ping to " + str(self.address))
self.token = chr(randint(1,255))
self.data = self._packet_response + str(ord(self.token)) + b'\x00'
self.request_time = time.time()
self._parent.sockets.get(self.address[0]).sendto(self._packet_request + self.token, self.address[1])
self._parent.add_handler(self)
def call(self, address, data):
#log('debug', "Server-callback hit from " + str(address))
self.parse(data[len(self.data):])
def parse(self, data):
self.latency = time.time() - self.request_time
data = iter(data.split(b'\x00'))
try:
self.version = data.next().decode('utf8')
self.name = data.next().decode('utf8')
self.map = data.next().decode('utf8')
self.gametype = data.next().decode('utf8')
self.password = (data.next()=='1')
self.players = int(data.next())
self.max_players = int(data.next())
self.clients = int(data.next())
self.max_clients = int(data.next())
for _ in range(self.clients):
player = Player()
player.name=data.next().decode('utf8')
player.clan=data.next().decode('utf8')
player.country = int(data.next())
player.score = int(data.next())
player.playing = (data.next()=='1')
player.server = self
self.playerlist.add(player)
except StopIteration:
log('warning', 'unexpected end of data for server ' + str(self))
def match(self, **kwargs):
if not kwargs.has_key("address") or kwargs["address"] != self.address:
return False
if not kwargs.has_key("data") or kwargs["data"][0:len(self.data)] != self.data:
return False
return True
def __repr__(self):
address = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
return "<MasterServer name='{name}' address='{address}'>" \
.format(name=self.name, address=address)
class Player(object):
def __init__(self):
self.name = ''
self.clan = ''
self.country = None
self.score = None
self.server = None
self.playing = False
def __repr__(self):
return "<Player name='{name}'>".format(name=self.name)
class ServerList(object):
def __init__(self):
self.servers = []
def add(self, server):
if not isinstance(server, Server):
raise Exception('Trying to add non-Server object')
self.servers.append(server)
def find(self, name=None, gametype=None, maxping=None):
output = []
for server in self.servers:
if (server.latency != -1) and \
(name == None or server.name == name) and \
(maxping == None or server.ping <= maxping) and \
(gametype == None or server.gametype == gametype):
output.append(server)
return output
def __repr__(self):
return str(self.servers)
class PlayerList(object):
def __init__(self):
self.players = []
def add(self, player):
if not isinstance(player, Player):
raise Exception('Trying to add non-Player-object')
self.players.append(player)
def find(self, name=None, clan=None, country=None, playing=None):
output = []
for player in self.players:
if (name == None or player.name == name) and \
(clan == None or player.clan == clan) and \
(country == None or player.country == country) and \
(playing == None or player.playing == playing):
output.append(player)
return output
def __repr__(self):
return str(self.players)
class Teeworlds(object):
def __init__(self, timeout=5):
#self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#self.socket.setblocking(0)
#self._socket.settimeout(2)
self.handlers = HandlerStorage()
self.serverlist = ServerList()
self.masterlist = []
self.sockets = MultiSocket(timeout)
def query_masters(self):
masters = ['master3.teeworlds.com'] #["master{0}.teeworlds.com".format(i) for i in range(2, 4+1)]
for mastername in masters:
# resolves host and picks the first address
try:
info = socket.getaddrinfo(mastername, 8300, 0, socket.SOCK_DGRAM)
except socket.gaierror as e:
log('warning', '[GAIError ' + e.errno + '] ' + e.strerror)
continue
else:
master_addr = (info[0][0], info[0][4])
log('debug', "requesting " + mastername + " " + str(master_addr))
master = MasterServer(self, master_addr, mastername.partition(".")[0])
master.request()
self.add_handler(master)
self.masterlist.append(master)
def run_loop(self):
while True:
try:
(r, w, x) = self.sockets.select(MultiSocket.READ)
if not r and not w and not x:
break
for sock in r:
(data, address) = sock.recvfrom(1500)
address = (self.sockets.getfamily(sock), address)
log('debug', "received data from socket: byteslen=" + str(len(data)) + " bytes=" + ' '.join([ "{0:2x}".format(ord(x)) for x in data[0:20] ]))
for handler in self.handlers.find(data=data, address=address):
log('debug', "calling handler " + repr(handler) + "with address=" + str(address))
handler.call(address, data)
except socket.timeout:
break
except socket.error as e:
# Errno 10054 happens when we get ICMP port unreachable, we don't care about that
if e.errno != 10054:
raise
def add_handler(self, handler):
# improve this
if not isinstance(handler, Handler):
raise Exception('Expecting instance of class Handler')
self.handlers.add(handler)
if __name__ == "__main__":
tw = Teeworlds(timeout=2)
tw.query_masters()
#s = Server(tw, (2, ('178.63.226.59', 8303)))
#tw.serverlist.add(s)
#s.request()
tw.run_loop()
for server in tw.serverlist.find():
print("{server: <64} on {master}: {clients: >2}/{max_clients: >2} - {latency: >4.0f} ms" \
.format(server=server.name, master=server.master.name, clients=server.clients, \
max_clients=server.max_clients, latency=server.latency*1000))
#print(str(tw.handlers))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment