|
1
|
1 |
|
import logging |
|
2
|
1 |
|
import secrets |
|
3
|
1 |
|
import socket |
|
4
|
|
|
|
|
5
|
1 |
|
from tw_serverinfo.models import Server |
|
6
|
|
|
|
|
7
|
|
|
|
|
8
|
1 |
|
class Network(object): |
|
9
|
|
|
# www.teeworlds.com has no AAAA domain record and doesn't support IPv6 only requests |
|
10
|
1 |
|
PROTOCOL_FAMILY = socket.AF_INET |
|
11
|
|
|
|
|
12
|
|
|
# connection timeout in ms |
|
13
|
1 |
|
CONNECTION_TIMEOUT = 1000 |
|
14
|
|
|
|
|
15
|
|
|
# sleep duration between checking if we received a packet in ms |
|
16
|
1 |
|
CONNECTION_SLEEP_DURATION = 50 |
|
17
|
|
|
|
|
18
|
1 |
|
PACKETS = { |
|
19
|
|
|
'SERVERBROWSE_GETCOUNT': b'\xff\xff\xff\xffcou2', |
|
20
|
|
|
'SERVERBROWSE_COUNT': b'\xff\xff\xff\xffsiz2', |
|
21
|
|
|
'SERVERBROWSE_GETLIST': b'\xff\xff\xff\xffreq2', |
|
22
|
|
|
'SERVERBROWSE_LIST': b'\xff\xff\xff\xfflis2', |
|
23
|
|
|
'SERVERBROWSE_GETINFO_64_LEGACY': b'\xff\xff\xff\xfffstd', |
|
24
|
|
|
'SERVERBROWSE_INFO_64_LEGACY': b'\xff\xff\xff\xffdtsf', |
|
25
|
|
|
'SERVERBROWSE_GETINFO': b'\xff\xff\xff\xffgie3', |
|
26
|
|
|
'SERVERBROWSE_INFO': b'\xff\xff\xff\xffinf3', |
|
27
|
|
|
'SERVERBROWSE_INFO_EXTENDED': b'\xff\xff\xff\xffiext', |
|
28
|
|
|
'SERVERBROWSE_INFO_EXTENDED_MORE': b'\xff\xff\xff\xffiex+', |
|
29
|
|
|
} |
|
30
|
|
|
|
|
31
|
1 |
|
@staticmethod |
|
32
|
1 |
|
def send_packet(sock: socket.socket, data: bytes, extra_data: bytes, server: Server, add_token=True) -> None: |
|
33
|
|
|
"""Generate or reuse a request token and send the passed data with the request token to the passed socket |
|
34
|
|
|
Returns the updated server dict with additional token on game server types |
|
35
|
|
|
|
|
36
|
|
|
:type sock: socket.socket |
|
37
|
|
|
:type data: bytes |
|
38
|
|
|
:type extra_data: bytes |
|
39
|
|
|
:type server: dict |
|
40
|
|
|
:type add_token: bool |
|
41
|
|
|
:return: |
|
42
|
|
|
""" |
|
43
|
1 |
|
if not server.request_token: |
|
44
|
1 |
|
server.request_token = secrets.token_bytes(nbytes=2) |
|
45
|
1 |
|
logging.log(logging.DEBUG, 'generated server request token: {token!r}'.format(token=server.request_token)) |
|
46
|
|
|
|
|
47
|
1 |
|
packet = b'%s%s\0\0%s' % (extra_data, server.request_token, data) |
|
48
|
|
|
|
|
49
|
1 |
|
if add_token: |
|
50
|
1 |
|
if not server.token: |
|
51
|
1 |
|
server.token = secrets.token_bytes(nbytes=1) |
|
52
|
1 |
|
logging.log(logging.DEBUG, 'generated server token: {token!r}'.format(token=server.token)) |
|
53
|
1 |
|
packet += server.token |
|
54
|
|
|
|
|
55
|
1 |
|
logging.log(logging.DEBUG, 'sending packet ({packet!r}) to {ip:s}:{port:d}'.format(packet=packet, ip=server.ip, |
|
56
|
|
|
port=server.port)) |
|
57
|
1 |
|
sock.sendto(packet, (server.ip, server.port)) |
|
58
|
|
|
|
|
59
|
1 |
|
@staticmethod |
|
60
|
1 |
|
def receive_packet(sock: socket.socket, servers: list, callback: callable) -> bool: |
|
61
|
|
|
"""Check if we received a packet if yes check for the servers with the ip and port |
|
62
|
|
|
and pass the server together with the data to the processing function given as a callback |
|
63
|
|
|
|
|
64
|
|
|
:param sock: |
|
65
|
|
|
:param servers: |
|
66
|
|
|
:param callback: |
|
67
|
|
|
:return: |
|
68
|
|
|
""" |
|
69
|
1 |
|
try: |
|
70
|
1 |
|
data, addr = sock.recvfrom(2048, 0) |
|
71
|
1 |
|
except BlockingIOError: |
|
72
|
1 |
|
return False |
|
73
|
|
|
|
|
74
|
1 |
|
logging.log(logging.DEBUG, 'received data ({data!r}) from {ip:s}:{port:d}'.format(data=data, ip=addr[0], |
|
75
|
|
|
port=addr[1])) |
|
76
|
1 |
|
for server in servers: # type: Server |
|
77
|
1 |
|
if server.ip == addr[0] and server.port == addr[1]: |
|
78
|
1 |
|
callback(data, server) |
|
79
|
1 |
|
return True |
|
80
|
|
|
return False |
|
81
|
|
|
|