Decode and save metadata acquired when querying new servers. Add a

metadata request message to retrieve the metadata that was obtained.

Subversion-branch: /master
Subversion-revision: 2194
This commit is contained in:
Simon Howard 2010-12-05 00:46:17 +00:00
parent 071f2aeffa
commit 2d40ae549a
2 changed files with 132 additions and 30 deletions

View file

@ -23,6 +23,7 @@
import socket
import struct
import simplejson
from select import select
from time import time, strftime
@ -50,32 +51,49 @@ NET_MASTER_PACKET_TYPE_ADD = 0
NET_MASTER_PACKET_TYPE_ADD_RESPONSE = 1
NET_MASTER_PACKET_TYPE_QUERY = 2
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE = 3
NET_MASTER_PACKET_TYPE_GET_METADATA = 4
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE = 5
# Address and port to listen on.
UDP_ADDRESS = socket.inet_ntoa(struct.pack(">l", socket.INADDR_ANY))
UDP_PORT = 2342
def read_string(packet):
""" Given binary packet data, read a NUL-terminated string, returning
the remainder of the packet data and the decoded string. """
terminator = struct.pack("b", 0)
if terminator not in packet:
raise Exception("String terminator not found")
strlen = packet.index(terminator)
result, = struct.unpack("%ss" % strlen, packet[0:strlen])
return packet[strlen + 1:], result
class Server:
""" A server that has registered itself. """
def __init__(self, addr):
self.addr = addr
self.verified = False
self.metadata = {}
self.refresh()
def refresh(self):
self.add_time = time()
def set_metadata(self, metadata):
self.metadata = metadata
def timed_out(self):
return time() - self.add_time > SERVER_TIMEOUT
def encode_addr(self):
s = "%s:%i" % self.addr
# Encode string along with terminating NUL.
return struct.pack("%is" % (len(s) + 1), s)
def __str__(self):
return "%s:%i" % self.addr
class MasterServer:
def open_log_file(self):
@ -100,10 +118,30 @@ class MasterServer:
""" Send a query to the specified server. """
packet = struct.pack(">h", NET_PACKET_TYPE_QUERY)
self.query_sock.sendto(packet, server.addr)
def parse_query_response(self, data, addr):
def parse_query_data(self, data):
""" Read the data from a query response. """
data, version = read_string(data)
server_state, num_players, max_players, mode, mission \
= struct.unpack("bbbbb", data[0:5])
data, server_name = read_string(data[5:])
# Not all of this is of interest to us. Some of it will
# be out of date fairly quickly because the master doesn't
# query the servers very often.
return {
"version": version,
"max_players": max_players,
"name": server_name
}
def process_query_response(self, data, addr):
""" Parse a packet received (presumably) in response to a
query that we sent to a server. """
@ -121,8 +159,11 @@ class MasterServer:
if packet_type != NET_PACKET_TYPE_QUERY_RESPONSE:
return
# TODO: Process rest of details so that we can maintain
# some information about list of servers?
# Read metadata from query and store it for future use.
metadata = self.parse_query_data(data[2:])
metadata["address"], metadata["port"] = addr
server.set_metadata(metadata)
# Server responded to our query, so it is verified.
# We can send a positive response to its add request.
@ -141,26 +182,24 @@ class MasterServer:
self.sock.sendto(packet, addr)
def response_packets(self):
""" Convert the list of servers into a list of payload strings
def strings_to_packets(self, strings):
""" Convert a list of strings into a list of payload strings
for responding to queries. """
packets = [struct.pack("")]
for server in self.servers.values():
encoded_addr = server.encode_addr()
for string in strings:
# Only include verified servers.
# Encode string along with terminating NUL.
if not server.verified:
continue
encoded_str = struct.pack("%is" % (len(string) + 1), string)
# Start a new packet?
if len(packets[-1]) + len(encoded_addr) > MAX_RESPONSE_LEN:
if len(packets[-1]) + len(encoded_str) > MAX_RESPONSE_LEN:
packets.append(struct.pack(""))
packets[-1] += encoded_addr
packets[-1] += encoded_str
return packets
@ -199,13 +238,38 @@ class MasterServer:
""" Process a query message received from a client. """
self.log_output(addr, "Query")
packets = self.response_packets()
for packet in packets:
# Generate a list of strings representing servers. Only include
# verified servers.
verified_servers = filter(lambda s: s.verified, self.servers.values())
strings = [ str(server) for server in verified_servers]
# Send response packets.
for packet in self.strings_to_packets(strings):
self.send_message(addr,
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE,
packet)
def process_metadata_request(self, addr):
""" Process a metadata request from a client. """
self.log_output(addr, "Metadata request")
# Generate a list of strings containing JSON-encoded metadata
# about servers. Only include verified servers.
verified_servers = filter(lambda s: s.verified, self.servers.values())
strings = [ simplejson.dumps(s.metadata) for s in verified_servers]
# Send response packets.
for packet in self.strings_to_packets(strings):
self.send_message(addr,
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE,
packet)
def process_packet(self, data, addr):
""" Process a packet received from a server. """
@ -215,6 +279,8 @@ class MasterServer:
self.process_add_to_master(addr)
elif packet_type == NET_MASTER_PACKET_TYPE_QUERY:
self.process_query(addr)
elif packet_type == NET_MASTER_PACKET_TYPE_GET_METADATA:
self.process_metadata_request(addr)
def rx_packet(self):
""" Invoked when a packet is received. """
@ -232,7 +298,7 @@ class MasterServer:
data, addr = self.query_sock.recvfrom(1024)
try:
self.parse_query_response(data, addr)
self.process_query_response(data, addr)
except Exception, e:
print e

View file

@ -24,11 +24,14 @@
import socket
import sys
import struct
import simplejson
NET_MASTER_PACKET_TYPE_ADD = 0
NET_MASTER_PACKET_TYPE_ADD_RESPONSE = 1
NET_MASTER_PACKET_TYPE_QUERY = 2
NET_MASTER_PACKET_TYPE_QUERY_RESPONSE = 3
NET_MASTER_PACKET_TYPE_GET_METADATA = 4
NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE = 5
UDP_PORT = 2342
@ -62,7 +65,7 @@ def read_string(packet):
terminator = struct.pack("b", 0)
strlen = packet.index(terminator)
result = struct.unpack("%ss" % strlen, packet[0:strlen])
result, = struct.unpack("%ss" % strlen, packet[0:strlen])
return packet[strlen + 1:], result
@ -91,15 +94,17 @@ def add_to_master(addr_str):
print "Address added to master."
def parse_query_response(packet):
servers = []
def decode_string_list(packet):
""" Decode binary data containing NUL-terminated strings. """
strings = []
while len(packet) > 0:
packet, addr_str = read_string(packet)
packet, string = read_string(packet)
servers.append(addr_str)
strings.append(string)
return servers
return strings
def query_master(addr_str):
""" Query a master server for its list of server IP addresses. """
@ -119,20 +124,51 @@ def query_master(addr_str):
response = get_response(sock, addr, NET_MASTER_PACKET_TYPE_QUERY_RESPONSE)
servers = parse_query_response(response)
servers = decode_string_list(response)
print "%i servers" % len(servers)
for s in servers:
print "\t%s" % s
def get_metadata(addr_str):
""" Query a master server for metadata about its servers. """
addr = (socket.gethostbyname(addr_str), UDP_PORT)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Send request
print "Sending metadata query to master at %s" % str(addr)
send_message(sock, addr, NET_MASTER_PACKET_TYPE_GET_METADATA)
# Receive response
print "Waiting for response..."
response = get_response(sock, addr, NET_MASTER_PACKET_TYPE_GET_METADATA_RESPONSE)
servers = decode_string_list(response)
print "%i servers" % len(servers)
for json in servers:
metadata = simplejson.loads(json)
print "\tServer: %s:%i" % (metadata["address"], metadata["port"])
print "\t\tName: %s" % metadata["name"]
print "\t\tVersion: %s" % metadata["version"]
print "\t\tMax. players: %i" % metadata["max_players"]
if len(sys.argv) > 2 and sys.argv[1] == "query":
query_master(sys.argv[2])
elif len(sys.argv) > 2 and sys.argv[1] == "add":
add_to_master(sys.argv[2])
elif len(sys.argv) > 2 and sys.argv[1] == "get-metadata":
get_metadata(sys.argv[2])
else:
print "Usage:"
print "chocolate-master-test.py query <address>"
print "chocolate-master-test.py add <address>"
print "chocolate-master-test.py get-metadata <address>"