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
|
|
|
|