1
|
1 |
|
import secrets |
2
|
1 |
|
import socket |
3
|
|
|
|
4
|
1 |
|
from tw_serverinfo.models import Server |
5
|
1 |
|
from tw_serverinfo.models.game_server import GameServer |
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, server: Server) -> 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 server: dict |
39
|
|
|
:return: |
40
|
|
|
""" |
41
|
1 |
|
if not server.request_token: |
42
|
1 |
|
server.request_token = secrets.token_bytes(nbytes=2) |
43
|
|
|
|
44
|
1 |
|
packet = b'xe%s\0\0%s' % (server.request_token, data) |
45
|
|
|
|
46
|
1 |
|
if isinstance(server, GameServer): |
47
|
1 |
|
if not server.token: |
48
|
1 |
|
server.token = secrets.token_bytes(nbytes=1) |
49
|
1 |
|
packet += server.token |
50
|
|
|
|
51
|
1 |
|
sock.sendto(packet, (server.ip, server.port)) |
52
|
|
|
|
53
|
1 |
|
@staticmethod |
54
|
1 |
|
def receive_packet(sock: socket.socket, servers: list, callback: callable) -> bool: |
55
|
|
|
"""Check if we received a packet if yes check for the servers with the ip and port |
56
|
|
|
and pass the server together with the data to the processing function given as a callback |
57
|
|
|
|
58
|
|
|
:param sock: |
59
|
|
|
:param servers: |
60
|
|
|
:param callback: |
61
|
|
|
:return: |
62
|
|
|
""" |
63
|
1 |
|
try: |
64
|
1 |
|
data, addr = sock.recvfrom(2048, 0) |
65
|
1 |
|
except BlockingIOError: |
66
|
1 |
|
return False |
67
|
|
|
|
68
|
1 |
|
for server in servers: # type: Server |
69
|
1 |
|
if server.ip == addr[0] and server.port == addr[1]: |
70
|
1 |
|
callback(data, server) |
71
|
|
|
|
72
|
|
|
return True |
73
|
|
|
|