Пример #1
0
def handle_natneg_preinit_ack(nn, recv_data, addr, socket):
    """Command: 0x10 - NN_PREINIT_ACK.

    Reply by the server for record NN_PREINIT (0x0F).

    Example:
    fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 00 00 00 00
    00 00

    After receiving other client's PREINIT:
    fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 01 02 00 00
    00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    04                - NATNEG version
    10                - NATNEG record type
    b5 e0 95 2a       - Session id
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - State (0x00 - Waiting for client,
                               0x01 - Waiting for matchup,
                               0x02 - Ready)
    00 00 00 00       - Other client's session id (or empty)
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_PREINIT_ACK (0x10)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #2
0
def handle_natneg_initack(nn, recv_data, addr, socket):
    """Command: 0x01 - NN_INITACK.

    Reply by the server for record NN_INIT (0x00).

    Example:
    fd fc 1e 66 6a b2 03 01 3d f1 00 71 00 00 ff ff
    6d 16 b5 7d ea

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    01                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    ff                - Use game port (-1)? Dummy value?
    ff 6d 16 b5       - Local IP? Dummy value?
    7d ea             - Local port? Hex speak of "Idea"? Dummy value?
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_INITACK (0x01)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
def handle_natneg_preinit_ack(nn, recv_data, addr, socket):
    """Command: 0x10 - NN_PREINIT_ACK.

    Reply by the server for record NN_PREINIT (0x0F).

    Example:
    fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 00 00 00 00
    00 00

    After receiving other client's PREINIT:
    fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 01 02 00 00
    00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    04                - NATNEG version
    10                - NATNEG record type
    b5 e0 95 2a       - Session id
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - State (0x00 - Waiting for client,
                               0x01 - Waiting for matchup,
                               0x02 - Ready)
    00 00 00 00       - Other client's session id (or empty)
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_PREINIT_ACK (0x10)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #4
0
def handle_natneg_address_reply(nn, recv_data, addr, socket):
    """Command: 0x0B - NN_ADDRESS_REPLY.

    Reply by the server for record NN_ADDRESS_CHECK (0x0A).

    Example:
    fd fc 1e 66 6a b2 03 0b 00 00 00 03 01 00 00 25
    c9 e2 8a 91 e4

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0b                - NATNEG record type
    00 00 00 03       - Session id
    01                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - Use game port
    25 c9 e2 8a       - Public IP
    91 e4             - Public port
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_ADDRESS_REPLY (0x0B)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_natify_request(nn, recv_data, addr, socket):
    """Command: 0x0C - NN_NATIFY_REQUEST.

    Send by the client during connection test.

    Example:
    fd fc 1e 66 6a b2 03 0c 00 00 03 09 01 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0c                - NATNEG record type
    00 00 03 09       - Session id
    01                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    port_type = "%02x" % ord(recv_data[12])
    logger.log(logging.DEBUG, "Received natify command from %s:%d...", *addr)

    output = bytearray(recv_data)
    output[7] = 0x02  # ERT Test
    nn.write_queue.put((output, addr, socket))

    logger.log(logging.DEBUG, "Sent natify response to %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
Пример #6
0
    def find_server(self, query_game, filter, fields, max_servers, game_name, challenge):
        # Get dictionary from master server list server.
        self.log(logging.DEBUG, "Searching for server matching '%s' with the fields '%s'" % (filter, fields))

        self.server_list = self.server_manager.find_servers(query_game, filter, fields, max_servers)._getvalue()

        self.log(logging.DEBUG, "Found server(s):")
        self.log(logging.DEBUG, self.server_list)

        if self.server_list == []:
            self.server_list.append({})

        for _server in self.server_list:
            server = _server
            if len(server) > 0 and len(fields) > 0 and server['requested'] == {}:
                # If the requested fields weren't found then don't return a server.
                # This fixes a bug with Mario Kart DS.
                #print "Requested was empty"
                server = {}

            if "__console__" in server:
                self.console = int(server['__console__'])

            # Generate binary server list data
            data = self.generate_server_list_data(self.address, fields, server)
            self.log(logging.DEBUG, utils.pretty_print_hex(data))

            # Encrypt data
            enc = gs_utils.EncTypeX()
            data = enc.encrypt(self.secret_key_list[game_name], challenge, data)

            # Send to client
            self.transport.write(bytes(data))
            self.log(logging.DEBUG, "Sent server list message to %s:%s..." % (self.address.host, self.address.port))
def handle_natneg_erttest(nn, recv_data, addr, socket):
    """Command: 0x02 - NN_ERTTEST.

    Reply by the server for record NN_NATIFY_REQUEST (0x0C).

    Example:
    fd fc 1e 66 6a b2 03 02 00 00 03 09 02 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    02                - NATNEG record type
    00 00 03 09       - Session id
    02                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_ERTTEST (0x02)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
def handle_natneg_report_ack(nn, recv_data, addr, socket):
    """Command: 0x0E - NN_REPORT_ACK.

    Reply by the server for record NN_REPORT (0x0D).

    Example:
    fd fc 1e 66 6a b2 03 0e 3d f1 00 71 00 00 00 00
    00 00 06 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0e                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (0x00, 0x80 or 0x90)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result (0x00 - Error,
                                       0x01 - Success)
    00 00 00 06       - NAT type (0x00 - No NAT,
                                  0x01 - Firewall only,
                                  0x02 - Full cone,
                                  0x03 - Restricted cone,
                                  0x04 - Port restricted cone,
                                  0x05 - Symmetric,
                                  0x06 - Unknown)
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_REPORT_ACK (0x0E)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #9
0
def handle_natneg_natify_request(nn, recv_data, addr, socket):
    """Command: 0x0C - NN_NATIFY_REQUEST.

    Send by the client during connection test.

    Example:
    fd fc 1e 66 6a b2 03 0c 00 00 03 09 01 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0c                - NATNEG record type
    00 00 03 09       - Session id
    01                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    port_type = "%02x" % ord(recv_data[12])
    logger.log(logging.DEBUG, "Received natify command from %s:%d...", *addr)

    output = bytearray(recv_data)
    output[7] = 0x02  # ERT Test
    nn.write_queue.put((output, addr, socket))

    logger.log(logging.DEBUG, "Sent natify response to %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_initack(nn, recv_data, addr, socket):
    """Command: 0x01 - NN_INITACK.

    Reply by the server for record NN_INIT (0x00).

    Example:
    fd fc 1e 66 6a b2 03 01 3d f1 00 71 00 00 ff ff
    6d 16 b5 7d ea

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    01                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    ff                - Use game port (-1)? Dummy value?
    ff 6d 16 b5       - Local IP? Dummy value?
    7d ea             - Local port? Hex speak of "Idea"? Dummy value?
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_INITACK (0x01)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #11
0
def handle_natneg_connect_ping(nn, recv_data, addr, socket):
    """Command: 0x07 - NN_CONNECT_PING.

    Looks like NN_CONNECT but between clients.
    The server shouldn't be involved.

    Example:
    fd fc 1e 66 6a b2 03 07 ?? ?? ?? ?? ?? ?? ?? ??
    ?? ?? ?? ??

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    07                - NATNEG record type
    ?? ?? ?? ??       - Session id
    ?? ?? ?? ??       - Remote IP
    ?? ??             - Remote port
    ??                - Sequence counter (0 or 1)
    ??                - Error
    """
    logger.log(
        logging.WARNING,
        "Received unimplemented command NN_CONNECT_PING (0x07)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #12
0
def handle_natneg_report_ack(nn, recv_data, addr, socket):
    """Command: 0x0E - NN_REPORT_ACK.

    Reply by the server for record NN_REPORT (0x0D).

    Example:
    fd fc 1e 66 6a b2 03 0e 3d f1 00 71 00 00 00 00
    00 00 06 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0e                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (0x00, 0x80 or 0x90)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result (0x00 - Error,
                                       0x01 - Success)
    00 00 00 06       - NAT type (0x00 - No NAT,
                                  0x01 - Firewall only,
                                  0x02 - Full cone,
                                  0x03 - Restricted cone,
                                  0x04 - Port restricted cone,
                                  0x05 - Symmetric,
                                  0x06 - Unknown)
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_REPORT_ACK (0x0E)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #13
0
def handle_natneg_erttest(nn, recv_data, addr, socket):
    """Command: 0x02 - NN_ERTTEST.

    Reply by the server for record NN_NATIFY_REQUEST (0x0C).

    Example:
    fd fc 1e 66 6a b2 03 02 00 00 03 09 02 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    02                - NATNEG record type
    00 00 03 09       - Session id
    02                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_ERTTEST (0x02)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
def handle_natneg_address_reply(nn, recv_data, addr, socket):
    """Command: 0x0B - NN_ADDRESS_REPLY.

    Reply by the server for record NN_ADDRESS_CHECK (0x0A).

    Example:
    fd fc 1e 66 6a b2 03 0b 00 00 00 03 01 00 00 25
    c9 e2 8a 91 e4

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0b                - NATNEG record type
    00 00 00 03       - Session id
    01                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - Use game port
    25 c9 e2 8a       - Public IP
    91 e4             - Public port
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_ADDRESS_REPLY (0x0B)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
Пример #15
0
    def forward_data_to_client(self, data, forward_client):
        # Find session id of server
        # Iterate through the list of servers sent to the client and match by IP and port.
        # Is there a better way to determine this information?
        if self.forward_client == None or len(self.forward_client) != 2:
            return

        server, ip = self.find_server_in_cache(self.forward_client[0],
                                               self.forward_client[1],
                                               self.console)

        if server == None:
            if self.console == 0:
                server, ip = self.find_server_in_cache(self.forward_client[0],
                                                       self.forward_client[1],
                                                       1)  # Try Wii
            elif self.console == 1:
                server, ip = self.find_server_in_cache(self.forward_client[0],
                                                       self.forward_client[1],
                                                       0)  # Try DS

        self.log(logging.DEBUG, "find_server_in_cache returned: %s" % server)
        self.log(
            logging.DEBUG, "Trying to send message to %s:%d..." %
            (self.forward_client[0], self.forward_client[1]))
        self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

        if server == None:
            return

        self.log(logging.DEBUG, "%s %s" % (ip, server['publicip']))
        if server['publicip'] == ip and server['publicport'] == str(
                self.forward_client[1]):
            # Send command to server to get it to connect to natneg
            natneg_session = int(
                utils.generate_random_hex_str(8), 16
            )  # Quick and lazy way to get a random 32bit integer. Replace with something else later

            output = bytearray([0xfe, 0xfd, 0x06])
            output += utils.get_bytes_from_int(server['__session__'])
            output += bytearray(utils.get_bytes_from_int(natneg_session))
            output += bytearray(data)

            if self.qr != None:
                self.qr.socket.sendto(output, forward_client)
                self.log(
                    logging.DEBUG,
                    "Forwarded data to %s:%s through QR server..." %
                    (forward_client[0], forward_client[1]))
            else:
                # In case we can't contact the QR server, just try sending the packet directly.
                # This isn't standard behavior but it can work in some instances.
                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, forward_client)
                self.log(
                    logging.DEBUG,
                    "Forwarded data to %s:%s directly (potential error occurred)..."
                    % (forward_client[0], forward_client[1]))
    def forward_data_to_client(self, data, forward_client):
        # Find session id of server
        # Iterate through the list of servers sent to the client and match by IP and port.
        # Is there a better way to determine this information?
        if self.forward_client == None or len(self.forward_client) != 2:
            return

        server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], self.console)

        if server == None:
            if self.console == 0:
                server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], 1) # Try Wii
            elif self.console == 1:
                server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], 0) # Try DS

        self.log(logging.DEBUG, "find_server_in_cache returned: %s" % server)
        self.log(logging.DEBUG, "Trying to send message to %s:%d..." % (self.forward_client[0], self.forward_client[1]))
        self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

        if server == None:
            return

        self.log(logging.DEBUG, "%s %s" % (ip, server['publicip']))
        if server['publicip'] == ip and server['publicport'] == str(self.forward_client[1]):
            if self.forward_client[1] == 0 and 'localport' in server:
                # No public port returned from client, try contacting on the local port.
                self.forward_client = (self.forward_client[0], int(server['localport']))

            # Send command to server to get it to connect to natneg
            cookie = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else later

            if len(data) == 10 and bytearray(data)[0:6] == bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
                natneg_session = utils.get_int(data,6)
                self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, server))
                self.server_manager.add_natneg_server(natneg_session, server) # Store info in backend so we can get it later in natneg

                # if self.qr != None:
                #     own_server = self.qr.get_own_server()
                #
                #     self.log(logging.DEBUG, "Adding %d to natneg server list: %s" % (natneg_session, own_server))
                #     self.server_manager.add_natneg_server(natneg_session, own_server) # Store info in backend so we can get it later in natneg

            output = bytearray([0xfe, 0xfd, 0x06])
            output += utils.get_bytes_from_int(server['__session__'])
            output += bytearray(utils.get_bytes_from_int(cookie))
            output += bytearray(data)

            if self.qr != None:
                self.log(logging.DEBUG, "Forwarded data to %s:%s through QR server..." % (forward_client[0], forward_client[1]))
                self.qr.socket.sendto(output, forward_client)
            else:
                # In case we can't contact the QR server, just try sending the packet directly.
                # This isn't standard behavior but it can work in some instances.
                self.log(logging.DEBUG, "Forwarded data to %s:%s directly (potential error occurred)..." % (forward_client[0], forward_client[1]))
                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, forward_client)
Пример #17
0
		def send_encrypted_data(self, challenge, data):
			self.log(logging.DEBUG, "Sent server list message to %s:%s..." % (self.address.host, self.address.port))
			self.log(logging.DEBUG, utils.pretty_print_hex(data))
			
			# Encrypt data
			enc = gs_utils.EncTypeX()
			data = enc.encrypt(self.secret_key_list[game_name], challenge, data)

			# Send to client
			self.transport.write(bytes(data))
        def send_encrypted_data(self, challenge, data):
            self.log(logging.DEBUG, "Sent server list message to %s:%s..." % (self.address.host, self.address.port))
            self.log(logging.DEBUG, utils.pretty_print_hex(data))
            
            # Encrypt data
            enc = gs_utils.EncTypeX()
            data = enc.encrypt(self.secret_key_list[game_name], challenge, data)

            # Send to client
            self.transport.write(bytes(data))
def handle_natneg_address_check(nn, recv_data, addr, socket):
    """Command: 0x0A - NN_ADDRESS_CHECK.

    Send by the client during connection test.

    Example:
    fd fc 1e 66 6a b2 03 0a 00 00 00 00 01 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0a                - NATNEG record type
    00 00 00 00       - Session id
    01                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    client_id = "%02x" % ord(recv_data[13])
    logger.log(logging.DEBUG,
               "Received address check command from %s:%d...",
               *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    output = bytearray(recv_data[0:15])
    output += utils.get_bytes_from_ip_str(addr[0])
    output += utils.get_bytes_from_short(addr[1], True)
    output += bytearray(recv_data[len(output):])

    output[7] = 0x0b  # NN_ADDRESS_REPLY
    nn.write_queue.put((output, addr, socket))

    logger.log(logging.DEBUG, "Sent address check response to %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
Пример #20
0
def handle_natneg_address_check(nn, recv_data, addr, socket):
    """Command: 0x0A - NN_ADDRESS_CHECK.

    Send by the client during connection test.

    Example:
    fd fc 1e 66 6a b2 03 0a 00 00 00 00 01 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0a                - NATNEG record type
    00 00 00 00       - Session id
    01                - Port type (between 0x00 and 0x03)
                      - 60 bytes padding?
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    00                - NATNEG result?
    00 00 00 00       - NAT type?
    00 00 00 00       - NAT mapping scheme?
    00 (x50)          - Game name?
    """
    client_id = "%02x" % ord(recv_data[13])
    logger.log(logging.DEBUG, "Received address check command from %s:%d...",
               *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    output = bytearray(recv_data[0:15])
    output += utils.get_bytes_from_ip_str(addr[0])
    output += utils.get_bytes_from_short(addr[1], True)
    output += bytearray(recv_data[len(output):])

    output[7] = 0x0b  # NN_ADDRESS_REPLY
    nn.write_queue.put((output, addr, socket))

    logger.log(logging.DEBUG, "Sent address check response to %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_preinit(nn, recv_data, addr, socket):
    """Command: 0x0F - NN_PREINIT.

    Natneg v4 command thanks to Pipian.
    Only seems to be used in very few DS games, namely,
    Pokemon Black/White/Black 2/White 2.

    Example:
    fd fc 1e 66 6a b2 04 0f b5 e0 95 2a 00 24 38 b2
    b3 5e

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    04                - NATNEG version
    0f                - NATNEG record type
    b5 e0 95 2a       - Session id
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    24                - State (0x00 - Waiting for client,
                               0x01 - Waiting for matchup,
                               0x02 - Ready)
    38 b2 b3 5e       - Other client's session id
    """
    logger.log(logging.DEBUG, "Received pre-init command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    session = utils.get_int(recv_data[-4:], 0)

    # Report response
    output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0])
    output[7] = 0x10  # Pre-init response

    if not session:
        # What's the correct behavior when session == 0?
        output[13] = 2
    elif session in nn.natneg_preinit_session:
        # Should this be sent to both clients or just the one that
        # connected most recently?
        # I can't tell from a one sided packet capture of Pokemon.
        # For the time being, send to both clients just in case.
        output[13] = 2
        nn.write_queue.put((output, nn.natneg_preinit_session[session],
                            socket))

        output[12] = (1, 0)[output[12]]  # Swap the index
        del nn.natneg_preinit_session[session]
    else:
        output[13] = 0
        nn.natneg_preinit_session[session] = addr

    nn.write_queue.put((output, addr, socket))
Пример #22
0
def handle_natneg_preinit(nn, recv_data, addr, socket):
    """Command: 0x0F - NN_PREINIT.

    Natneg v4 command thanks to Pipian.
    Only seems to be used in very few DS games, namely,
    Pokemon Black/White/Black 2/White 2.

    Example:
    fd fc 1e 66 6a b2 04 0f b5 e0 95 2a 00 24 38 b2
    b3 5e

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    04                - NATNEG version
    0f                - NATNEG record type
    b5 e0 95 2a       - Session id
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    24                - State (0x00 - Waiting for client,
                               0x01 - Waiting for matchup,
                               0x02 - Ready)
    38 b2 b3 5e       - Other client's session id
    """
    logger.log(logging.DEBUG, "Received pre-init command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    session = utils.get_int(recv_data[-4:], 0)

    # Report response
    output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0])
    output[7] = 0x10  # Pre-init response

    if not session:
        # What's the correct behavior when session == 0?
        output[13] = 2
    elif session in nn.natneg_preinit_session:
        # Should this be sent to both clients or just the one that
        # connected most recently?
        # I can't tell from a one sided packet capture of Pokemon.
        # For the time being, send to both clients just in case.
        output[13] = 2
        nn.write_queue.put(
            (output, nn.natneg_preinit_session[session], socket))

        output[12] = (1, 0)[output[12]]  # Swap the index
        del nn.natneg_preinit_session[session]
    else:
        output[13] = 0
        nn.natneg_preinit_session[session] = addr

    nn.write_queue.put((output, addr, socket))
Пример #23
0
def handle_natneg_stateupdate(nn, recv_data, addr, socket):
    """Command: 0x04 - NN_STATEUPDATE.

    TODO

    Example:
    TODO

    Description:
    TODO
    """
    logger.log(
        logging.WARNING, "Received unimplemented command NN_STATEUPDATE (0x04)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_stateupdate(nn, recv_data, addr, socket):
    """Command: 0x04 - NN_STATEUPDATE.

    TODO

    Example:
    TODO

    Description:
    TODO
    """
    logger.log(logging.WARNING,
               "Received unimplemented command NN_STATEUPDATE (0x04)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_backup_ack(nn, recv_data, addr, socket):
    """Command: 0x09 - NN_BACKUP_ACK.

    Reply by the server for record NN_BACKUP_TEST (0x08).
    Only the record type is changed.

    Example:
    TODO

    Description:
    TODO
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_BACKUP_ACK (0x09)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
Пример #26
0
def handle_natneg_backup_ack(nn, recv_data, addr, socket):
    """Command: 0x09 - NN_BACKUP_ACK.

    Reply by the server for record NN_BACKUP_TEST (0x08).
    Only the record type is changed.

    Example:
    TODO

    Description:
    TODO
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_BACKUP_ACK (0x09)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def handle_natneg_backup_test(nn, recv_data, addr, socket):
    """Command: 0x08 - NN_BACKUP_TEST.

    Send by the client.

    Example:
    TODO

    Description:
    Untested
    """
    logger.log(logging.DEBUG, "Received backup command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    # Backup response
    output = bytearray(recv_data)
    output[7] = 0x09  # NN_BACKUP_ACK
    nn.write_queue.put((output, addr, socket))
Пример #28
0
def handle_natneg_backup_test(nn, recv_data, addr, socket):
    """Command: 0x08 - NN_BACKUP_TEST.

    Send by the client.

    Example:
    TODO

    Description:
    Untested
    """
    logger.log(logging.DEBUG, "Received backup command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    # Backup response
    output = bytearray(recv_data)
    output[7] = 0x09  # NN_BACKUP_ACK
    nn.write_queue.put((output, addr, socket))
Пример #29
0
def handle_natneg_report(nn, recv_data, addr, socket):
    """Command: 0x0D - NN_REPORT.

    Send by the client.

    Example:
    fd fc 1e 66 6a b2 03 0d 3d f1 00 71 00 00 01 00
    00 00 06 00 00 00 00 6d 61 72 69 6f 6b 61 72 74
    77 69 69 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0d                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (0x00, 0x80 or 0x90)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    01                - NATNEG result (0x00 - Error,
                                       0x01 - Success)
    00 00 00 06       - NAT type (0x00 - No NAT,
                                  0x01 - Firewall only,
                                  0x02 - Full cone,
                                  0x03 - Restricted cone,
                                  0x04 - Port restricted cone,
                                  0x05 - Symmetric,
                                  0x06 - Unknown)
    00 00 00 00       - NAT mapping scheme (0x00 - Unknown,
                                            0x01 - Private same as public,
                                            0x02 - Consistent port,
                                            0x03 - Incremental,
                                            0x04 - Mixed)
    GAME_NAME 00      - Game name (GAME_NAME is 49 bytes length)
    """
    logger.log(logging.DEBUG, "Received report command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    # Report response
    output = bytearray(recv_data[:21])
    output[7] = 0x0e  # Report response
    output[14] = 0  # Clear byte to match real server's response
    nn.write_queue.put((output, addr, socket))
def handle_natneg_report(nn, recv_data, addr, socket):
    """Command: 0x0D - NN_REPORT.

    Send by the client.

    Example:
    fd fc 1e 66 6a b2 03 0d 3d f1 00 71 00 00 01 00
    00 00 06 00 00 00 00 6d 61 72 69 6f 6b 61 72 74
    77 69 69 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    0d                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (0x00, 0x80 or 0x90)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    01                - NATNEG result (0x00 - Error,
                                       0x01 - Success)
    00 00 00 06       - NAT type (0x00 - No NAT,
                                  0x01 - Firewall only,
                                  0x02 - Full cone,
                                  0x03 - Restricted cone,
                                  0x04 - Port restricted cone,
                                  0x05 - Symmetric,
                                  0x06 - Unknown)
    00 00 00 00       - NAT mapping scheme (0x00 - Unknown,
                                            0x01 - Private same as public,
                                            0x02 - Consistent port,
                                            0x03 - Incremental,
                                            0x04 - Mixed)
    GAME_NAME 00      - Game name (GAME_NAME is 49 bytes length)
    """
    logger.log(logging.DEBUG, "Received report command from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

    # Report response
    output = bytearray(recv_data[:21])
    output[7] = 0x0e  # Report response
    output[14] = 0  # Clear byte to match real server's response
    nn.write_queue.put((output, addr, socket))
    def handle(self):
        """Handle NAT Negotiation request."""
        recv_data, socket = self.request
        addr = self.client_address

        logger.log(logging.DEBUG, "Connection from %s:%d...", *addr)
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

        # Make sure it's a legal packet
        if not recv_data.startswith(self.nn_magics):
            logger.log(logging.ERROR, "Aborted due to illegal packet!")
            return

        # Handle commands
        try:
            command = self.nn_commands.get(recv_data[7], handle_natneg)
            command(self.server, recv_data, addr, socket)
        except:
            logger.log(logging.ERROR, "Failed to handle command!")
            logger.log(logging.ERROR, "%s", traceback.format_exc())
Пример #32
0
    def handle(self):
        """Handle NAT Negotiation request."""
        recv_data, socket = self.request
        addr = self.client_address

        logger.log(logging.DEBUG, "Connection from %s:%d...", *addr)
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

        # Make sure it's a legal packet
        if not recv_data.startswith(self.nn_magics):
            logger.log(logging.ERROR, "Aborted due to illegal packet!")
            return

        # Handle commands
        try:
            command = self.nn_commands.get(recv_data[7], handle_natneg)
            command(self.server, recv_data, addr, socket)
        except:
            logger.log(logging.ERROR, "Failed to handle command!")
            logger.log(logging.ERROR, "%s", traceback.format_exc())
    def forward_data_to_client(self, data, forward_client):
        # Find session id of server
        # Iterate through the list of servers sent to the client and match by IP and port.
        # Is there a better way to determine this information?
        if self.forward_client == None or len(self.forward_client) != 2:
            return

        server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], self.console)

        if server == None:
            if self.console == 0:
                server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], 1) # Try Wii
            elif self.console == 1:
                server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], 0) # Try DS

        self.log(logging.DEBUG, "find_server_in_cache returned: %s" % server)
        self.log(logging.DEBUG, "Trying to send message to %s:%d..." % (self.forward_client[0], self.forward_client[1]))
        self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

        if server == None:
            return

        self.log(logging.DEBUG, "%s %s" % (ip, server['publicip']))
        if server['publicip'] == ip and server['publicport'] == str(self.forward_client[1]):
            # Send command to server to get it to connect to natneg
            natneg_session = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else later

            output = bytearray([0xfe, 0xfd, 0x06])
            output += utils.get_bytes_from_int(server['__session__'])
            output += bytearray(utils.get_bytes_from_int(natneg_session))
            output += bytearray(data)

            if self.qr != None:
                self.qr.socket.sendto(output, forward_client)
                self.log(logging.DEBUG, "Forwarded data to %s:%s through QR server..." % (forward_client[0], forward_client[1]))
            else:
                # In case we can't contact the QR server, just try sending the packet directly.
                # This isn't standard behavior but it can work in some instances.
                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, forward_client)
                self.log(logging.DEBUG, "Forwarded data to %s:%s directly (potential error occurred)..." % (forward_client[0], forward_client[1]))
def handle_natneg_connect(nn, recv_data, addr, socket):
    """Command: 0x05 - NN_CONNECT.

    Reply by the server for record NN_INIT (0x00).

    Example:
    fd fc 1e 66 6a b2 03 05 3d f1 00 71 18 ab ed 7a
    da 00 42 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    05                - NATNEG record type
    3d f1 00 71       - Session id
    18 ab ed 7a       - Remote IP
    da 00             - Remote port
    42                - Got remote data
    00                - Finished
    """
    logger.log(logging.WARNING,
               "Received server record type command NN_CONNECT (0x05)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
def handle_natneg_connect_ping(nn, recv_data, addr, socket):
    """Command: 0x07 - NN_CONNECT_PING.

    Looks like NN_CONNECT but between clients.
    The server shouldn't be involved.

    Example:
    fd fc 1e 66 6a b2 03 07 ?? ?? ?? ?? ?? ?? ?? ??
    ?? ?? ?? ??

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    07                - NATNEG record type
    ?? ?? ?? ??       - Session id
    ?? ?? ?? ??       - Remote IP
    ?? ??             - Remote port
    ??                - Sequence counter (0 or 1)
    ??                - Error
    """
    logger.log(logging.WARNING,
               "Received unimplemented command NN_CONNECT_PING (0x07)"
               " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
Пример #36
0
def handle_natneg_connect(nn, recv_data, addr, socket):
    """Command: 0x05 - NN_CONNECT.

    Reply by the server for record NN_INIT (0x00).

    Example:
    fd fc 1e 66 6a b2 03 05 3d f1 00 71 18 ab ed 7a
    da 00 42 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    05                - NATNEG record type
    3d f1 00 71       - Session id
    18 ab ed 7a       - Remote IP
    da 00             - Remote port
    42                - Got remote data
    00                - Finished
    """
    logger.log(
        logging.WARNING,
        "Received server record type command NN_CONNECT (0x05)"
        " from %s:%d...", *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
    def forward_data_to_client(self, data, forward_client):
        # Find session id of server
        # Iterate through the list of servers sent to the client and match by
        # IP and port. Is there a better way to determine this information?
        if forward_client is None or len(forward_client) != 2:
            return

        server, ip = self.find_server_in_cache(forward_client[0],
                                               forward_client[1], self.console)

        if server is None:
            if self.console == 0:
                server, ip = self.find_server_in_cache(forward_client[0],
                                                       forward_client[1],
                                                       1)  # Try Wii
            elif self.console == 1:
                server, ip = self.find_server_in_cache(forward_client[0],
                                                       forward_client[1],
                                                       0)  # Try DS

        self.log(logging.DEBUG,
                 "find_server_in_cache returned: %s",
                 server)
        self.log(logging.DEBUG,
                 "Trying to send message to %s:%d...",
                 forward_client[0], forward_client[1])
        self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(data)))

        if server is None:
            return

        self.log(logging.DEBUG, "%s %s", ip, server['publicip'])
        if server['publicip'] == ip and \
           server['publicport'] == str(forward_client[1]):
            if forward_client[1] == 0 and 'localport' in server:
                # No public port returned from client, try contacting on
                # the local port.
                forward_client = (forward_client[0], int(server['localport']))

            # Send command to server to get it to connect to natneg
            # Quick and lazy way to get a random 32bit integer. Replace with
            # something else later
            cookie = int(utils.generate_random_hex_str(8), 16)

            # if (len(data) == 24 and bytearray(data)[0:10] == \
            #     bytearray([0x53, 0x42, 0x43, 0x4d, 0x03,
            #                0x00, 0x00, 0x00, 0x01, 0x04])) or \
            #     (len(data) == 40 and bytearray(data)[0:10] == \
            #                          bytearray([0x53, 0x42, 0x43, 0x4d,
            #                                     0x0b, 0x00, 0x00, 0x00,
            #                                     0x01, 0x04])):
            if self.own_server is None and len(data) >= 16 and \
               bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]),
                                        bytearray([0x53, 0x42, 0x43, 0x4d])):
                # Is the endianness the same between the DS and Wii here?
                # It seems so but I'm not positive.
                # Note to self: Port is little endian here.
                self_port = utils.get_short(bytearray(data[10:12]), 0, False)
                self_ip = '.'.join(["%d" % x for x in bytearray(data[12:16])])

                self.own_server, _ = self.find_server_in_cache(self_ip,
                                                               self_port,
                                                               self.console)

                if self.own_server is None:
                    if self.console == 0:
                        # Try Wii
                        self.own_server, _ = self.find_server_in_cache(
                            self_ip, self_port, 1
                        )
                    elif self.console == 1:
                        # Try DS
                        self.own_server, _ = self.find_server_in_cache(
                            self_ip, self_port, 0
                        )

                if self.own_server is None:
                    self.log(logging.DEBUG,
                             "Could not find own server: %s:%d",
                             self_ip, self_port)
                else:
                    self.log(logging.DEBUG,
                             "Found own server: %s",
                             self.own_server)

            elif len(data) == 10 and \
                    bytearray(data)[0:6] == \
                    bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
                natneg_session = utils.get_int_signed(data, 6)
                self.log(logging.DEBUG,
                         "Adding %d to natneg server list: %s",
                         natneg_session, server)
                # Store info in backend so we can get it later in natneg
                self.server_manager.add_natneg_server(natneg_session, server)

                if self.own_server is not None:
                    self.log(logging.DEBUG,
                             "Adding %d to natneg server list: %s (self)",
                             natneg_session, self.own_server)
                    # Store info in backend so we can get it later in natneg
                    self.server_manager.add_natneg_server(natneg_session,
                                                          self.own_server)

                # if self.qr is not None:
                #     own_server = self.qr.get_own_server()
                #
                #     self.log(logging.DEBUG,
                #              "Adding %d to natneg server list: %s",
                #              natneg_session, own_server)
                #     self.server_manager.add_natneg_server(natneg_session,
                #                                           own_server)

            output = bytearray([0xfe, 0xfd, 0x06])
            output += utils.get_bytes_from_int(server['__session__'])
            output += bytearray(utils.get_bytes_from_int(cookie))
            output += bytearray(data)

            if self.qr is not None:
                self.log(logging.DEBUG,
                         "Forwarded data to %s:%s through QR server...",
                         forward_client[0], forward_client[1])
                self.qr.socket.sendto(output, forward_client)
            else:
                # In case we can't contact the QR server, just try sending
                # the packet directly. This isn't standard behavior but it
                # can work in some instances.
                self.log(logging.DEBUG,
                         "Forwarded data to %s:%s directly"
                         " (potential error occurred)...",
                         forward_client[0], forward_client[1])
                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, forward_client)
Пример #38
0
def handle_natneg_init(nn, recv_data, addr, socket):
    """Command: 0x00 - NN_INIT.

    Send by the client to initialize the connection.

    Example:
    fd fc 1e 66 6a b2 03 00 3d f1 00 71 00 00 01 0a
    00 01 e2 00 00 6d 61 72 69 6f 6b 61 72 74 77 69
    69 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    00                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    01                - Use game port
    0a 00 01 e2       - Local IP
    00 00             - Local port
    GAME_NAME 00      - Game name
    """
    logger.log(logging.DEBUG, "Received initialization from %s:%d...", *addr)

    session_id = utils.get_int(recv_data, 8)
    output = bytearray(recv_data[0:14])

    # Checked with Tetris DS, Mario Kart DS, and Metroid Prime
    # Hunters, and this seems to be the standard response to 0x00
    output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea])
    output[7] = 0x01  # Initialization response
    nn.write_queue.put((output, addr, socket))

    # Try to connect to the server
    gameid = utils.get_string(recv_data, 0x15)
    client_id = "%02x" % ord(recv_data[13])
    localaddr = utils.get_local_addr(recv_data, 15)

    nn.session_list \
        .setdefault(session_id, {}) \
        .setdefault(client_id,
                    {
                        'connected': False,
                        'addr': '',
                        'localaddr': None,
                        'serveraddr': None,
                        'gameid': None
                    })

    # In fact, it's a pointer
    client_id_session = nn.session_list[session_id][client_id]
    client_id_session['gameid'] = gameid
    client_id_session['addr'] = addr
    client_id_session['localaddr'] = localaddr

    for client in nn.session_list[session_id]:
        # Another pointer
        client_session = nn.session_list[session_id][client]
        if client_session['connected'] or client == client_id:
            continue

        # --- Send to requesting client
        # Get server info
        serveraddr = nn.get_server_addr(gameid, session_id, client)
        client_session['serveraddr'] = serveraddr
        logger.log(logging.DEBUG,
                   "Found server from local ip/port: %s from %d", serveraddr,
                   session_id)

        # Get public port
        if client_session['serveraddr'] is not None:
            publicport = int(client_session['serveraddr']['publicport'])
        else:
            publicport = \
                client_session['localaddr'][1] or \
                client_session['addr'][1]

        output = bytearray(recv_data[0:12])
        output += utils.get_bytes_from_ip_str(client_session['addr'][0])
        output += utils.get_bytes_from_short(publicport, True)

        # Unknown, always seems to be \x42\x00
        output += bytearray([0x42, 0x00])
        output[7] = 0x05  # NN_CONNECT
        nn.write_queue.put((output, client_id_session['addr'], socket))

        logger.log(logging.DEBUG, "Sent connection request to %s:%d...",
                   *client_id_session['addr'])
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))

        # --- Send to other client
        # Get server info
        serveraddr = nn.get_server_addr(gameid, session_id, client_id)
        client_id_session['serveraddr'] = serveraddr
        logger.log(logging.DEBUG,
                   "Found server 2 from local ip/port: %s from %d", serveraddr,
                   session_id)

        # Get public port
        if client_id_session['serveraddr'] is not None:
            publicport = int(client_id_session['serveraddr']['publicport'])
        else:
            publicport = \
                client_id_session['localaddr'][1] or \
                client_id_session['addr'][1]

        output = bytearray(recv_data[0:12])
        output += utils.get_bytes_from_ip_str(client_id_session['addr'][0])
        output += utils.get_bytes_from_short(publicport, True)

        # Unknown, always seems to be \x42\x00
        output += bytearray([0x42, 0x00])
        output[7] = 0x05  # NN_CONNECT
        nn.write_queue.put((output, client_session['addr'], socket))

        logger.log(logging.DEBUG, "Sent connection request to %s:%d...",
                   *client_session['addr'])
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
Пример #39
0
    def handle_packet(self, recv_data, addr):
        logger.log(logging.DEBUG,
                   "Connection from %s:%d..." % (addr[0], addr[1]))
        logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

        # Make sure it's a legal packet
        if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
            return

        session_id = struct.unpack("<I", recv_data[8:12])[0]
        session_id_raw = recv_data[8:12]

        # Handle commands
        if recv_data[7] == '\x00':
            logger.log(
                logging.DEBUG,
                "Received initialization from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data[0:14])
            output += bytearray(
                [0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]
            )  # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00
            output[7] = 0x01  # Initialization response
            self.write_queue.put((output, addr))

            # Try to connect to the server
            gameid = utils.get_string(recv_data, 0x15)
            client_id = "%02x" % ord(recv_data[13])

            localip_raw = recv_data[15:19]
            localip_int_le = utils.get_ip(recv_data, 15)
            localip_int_be = utils.get_ip(recv_data, 15, True)
            localip = '.'.join(["%d" % ord(x) for x in localip_raw])
            localport_raw = recv_data[19:21]
            localport = utils.get_short(localport_raw, 0, True)
            localaddr = (localip, localport, localip_int_le, localip_int_be)

            self.session_list.setdefault(session_id, {}).setdefault(
                client_id, {
                    'connected': False,
                    'addr': '',
                    'localaddr': None,
                    'serveraddr': None,
                    'gameid': None
                })

            self.session_list[session_id][client_id]['gameid'] = gameid
            self.session_list[session_id][client_id]['addr'] = addr
            self.session_list[session_id][client_id]['localaddr'] = localaddr
            clients = len(self.session_list[session_id])

            for client in self.session_list[session_id]:
                if self.session_list[session_id][client][
                        'connected'] == False:  # and self.session_list[session_id][client]['localaddr'][1] != 0:
                    if client == client_id:
                        continue

                    #if self.session_list[session_id][client]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id,
                                                      client)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(
                            gameid, session_id, client)

                    self.session_list[session_id][client][
                        'serveraddr'] = serveraddr
                    logger.log(
                        logging.DEBUG,
                        "Found server from local ip/port: %s from %d" %
                        (serveraddr, session_id))

                    publicport = self.session_list[session_id][client]['addr'][
                        1]
                    if self.session_list[session_id][client]['localaddr'][
                            1] != 0:
                        publicport = self.session_list[session_id][client][
                            'localaddr'][1]

                    if self.session_list[session_id][client][
                            'serveraddr'] != None:
                        publicport = int(self.session_list[session_id][client]
                                         ['serveraddr']['publicport'])

                    # Send to requesting client
                    output = bytearray(recv_data[0:12])
                    output += bytearray([
                        int(x) for x in self.session_list[session_id][client]
                        ['addr'][0].split('.')
                    ])
                    output += utils.get_bytes_from_short(publicport, True)

                    output += bytearray(
                        [0x42, 0x00])  # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #self.write_queue.put((output, (self.session_list[session_id][client_id]['addr'])))
                    self.write_queue.put((output, (
                        self.session_list[session_id][client_id]['addr'][0],
                        self.session_list[session_id][client_id]['addr'][1])))

                    logger.log(
                        logging.DEBUG, "Sent connection request to %s:%d..." %
                        (self.session_list[session_id][client_id]['addr'][0],
                         self.session_list[session_id][client_id]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

                    # Send to other client
                    #if self.session_list[session_id][client_id]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id,
                                                      client_id)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(
                            gameid, session_id, client_id)

                    self.session_list[session_id][client_id][
                        'serveraddr'] = serveraddr
                    logger.log(
                        logging.DEBUG,
                        "Found server 2 from local ip/port: %s from %d" %
                        (serveraddr, session_id))

                    publicport = self.session_list[session_id][client_id][
                        'addr'][1]
                    if self.session_list[session_id][client_id]['localaddr'][
                            1] != 0:
                        publicport = self.session_list[session_id][client_id][
                            'localaddr'][1]

                    if self.session_list[session_id][client_id][
                            'serveraddr'] != None:
                        publicport = int(
                            self.session_list[session_id][client_id]
                            ['serveraddr']['publicport'])

                    output = bytearray(recv_data[0:12])
                    output += bytearray([
                        int(x) for x in self.session_list[session_id]
                        [client_id]['addr'][0].split('.')
                    ])
                    output += utils.get_bytes_from_short(publicport, True)

                    output += bytearray(
                        [0x42, 0x00])  # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #self.write_queue.put((output, (self.session_list[session_id][client]['addr'])))
                    self.write_queue.put(
                        (output,
                         (self.session_list[session_id][client]['addr'][0],
                          self.session_list[session_id][client]['addr'][1])))

                    logger.log(
                        logging.DEBUG, "Sent connection request to %s:%d..." %
                        (self.session_list[session_id][client]['addr'][0],
                         self.session_list[session_id][client]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x06':  # Was able to connect
            client_id = "%02x" % ord(recv_data[13])
            logger.log(
                logging.DEBUG, "Received connected command from %s:%s..." %
                (addr[0], addr[1]))

            if session_id in self.session_list and client_id in self.session_list[
                    session_id]:
                self.session_list[session_id][client_id]['connected'] = True

        elif recv_data[7] == '\x0a':  # Address check. Note: UNTESTED!
            client_id = "%02x" % ord(recv_data[13])
            logger.log(
                logging.DEBUG, "Received address check command from %s:%s..." %
                (addr[0], addr[1]))

            output = bytearray(recv_data[0:15])
            output += bytearray([int(x) for x in addr[0].split('.')])
            output += utils.get_bytes_from_short(addr[1], True)
            output += bytearray(recv_data[len(output):])

            output[7] = 0x0b
            self.write_queue.put((output, addr))

            logger.log(
                logging.DEBUG,
                "Sent address check response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0c':  # Natify
            port_type = "%02x" % ord(recv_data[12])
            logger.log(
                logging.DEBUG,
                "Received natify command from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data)
            output[7] = 0x02  # ERT Test
            self.write_queue.put((output, addr))

            logger.log(logging.DEBUG,
                       "Sent natify response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0d':  # Report
            logger.log(
                logging.DEBUG,
                "Received report command from %s:%s..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            # Report response
            output = bytearray(recv_data[:21])
            output[7] = 0x0e  # Report response
            output[14] = 0  # Clear byte to match real server's response
            self.write_queue.put((output, addr))

        elif recv_data[7] == '\x0f':
            # Natneg v4 command thanks to Pipian.
            # Only seems to be used in very few DS games (namely, Pokemon Black/White/Black 2/White 2).
            logger.log(
                logging.DEBUG,
                "Received pre-init command from %s:%s..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            session = utils.get_int(recv_data[-4:], 0)

            # Report response
            output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0])
            output[7] = 0x10  # Pre-init response

            if session == 0:
                # What's the correct behavior when session == 0?
                output[13] = 2
            elif session in self.natneg_preinit_session:
                # Should this be sent to both clients or just the one that connected most recently?
                # I can't tell from a one sided packet capture of Pokemon.
                # For the time being, send to both clients just in case.
                output[13] = 2
                self.write_queue.put(
                    (output, self.natneg_preinit_session[session]))

                output[12] = (1, 0)[output[12]]  # Swap the index
                del self.natneg_preinit_session[session]
            else:
                output[13] = 0
                self.natneg_preinit_session[session] = addr

            self.write_queue.put((output, addr))

        else:  # Was able to connect
            logger.log(
                logging.DEBUG, "Received unknown command %02x from %s:%s..." %
                (ord(recv_data[7]), addr[0], addr[1]))
    def rawDataReceived(self, data):
        # First 2 bytes are the packet size.
        #
        # Third byte is the command byte.
        # According to Openspy-Core:
        #   0x00 - Server list request
        #   0x01 - Server info request
        #   0x02 - Send message request
        #   0x03 - Keep alive reply
        #   0x04 - Map loop request (?)
        #   0x05 - Player search request
        #
        # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented.

        if self.forward_to_client:
            self.forward_to_client = False

            # Find session id of server
            # Iterate through the list of servers sent to the client and match by IP and port.
            # Is there a better way to determine this information?
            if self.console != 0:
                ip = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in self.forward_client[0].split('.')]), 0)).value) # Wii
            else:
                ip = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in self.forward_client[0].split('.')]), 0)).value) # DS

            logger.log(logging.DEBUG, "Trying to send message to %s:%d..." % (self.forward_client[0], self.forward_client[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

            # Get server based on ip/port
            server = self.server_manager.find_server_by_address(ip, self.forward_client[1])._getvalue()
            logger.log(logging.DEBUG, "find_server_by_address returned: %s" % server)

            if server == None:
                pass

            logger.log(logging.DEBUG, "%s %s" % (ip, server['publicip']))
            if server['publicip'] == ip and server['publicport'] == str(self.forward_client[1]):
                # Send command to server to get it to connect to natneg
                natneg_session = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else late.r

                output = bytearray([0xfe, 0xfd, 0x06])
                output += utils.get_bytes_from_int(server['__session__'])
                output += bytearray(utils.get_bytes_from_int(natneg_session))
                output += bytearray(data)

                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, self.forward_client)
                logger.log(logging.DEBUG, "Forwarded data to %s:%s..." % (self.forward_client[0], self.forward_client[1]))
            return

        if data[2] == '\x00': # Server list request
            logger.log(logging.DEBUG, "Received server list request from %s:%s..." % (self.address.host, self.address.port))

            # This code is so... not python. The C programmer in me is coming out strong.
            # TODO: Rewrite this section later?
            idx = 3
            list_version = ord(data[idx])
            idx += 1
            encoding_version = ord(data[idx])
            idx += 1
            game_version = utils.get_int(data, idx)
            idx += 4

            query_game = utils.get_string(data, idx)
            idx += len(query_game) + 1
            game_name = utils.get_string(data, idx)
            idx += len(game_name) + 1

            challenge = data[idx:idx+8]
            idx += 8

            filter = utils.get_string(data, idx)
            idx += len(filter) + 1
            fields = utils.get_string(data, idx)
            idx += len(fields) + 1

            options = utils.get_int_be(data, idx)
            idx += 4

            source_ip = 0
            max_servers = 0

            ALTERNATE_SOURCE_IP = 0x08
            LIMIT_RESULT_COUNT = 0x80
            if (options & LIMIT_RESULT_COUNT):
                max_servers = utils.get_int(data, idx)
            elif (options & ALTERNATE_SOURCE_IP):
                source_ip = utils.get_int(data, idx)

            if '\\' in fields:
                fields = [x for x in fields.split('\\') if x and not x.isspace()]

            #print "%02x %02x %08x" % (list_version, encoding_version, game_version)
            #print "%s" % query_game
            #print "%s" % game_name
            #print "%s" % challenge
            #print "%s" % filter
            #print "%s" % fields

            #print "%08x" % options
            #print "%d %08x" % (max_servers, source_ip)

            logger.log(logging.DEBUG, "list version: %02x / encoding version: %02x / game version: %08x / query game: %s / game name: %s / challenge: %s / filter: %s / fields: %s / options: %08x / max servers: %d / source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip))

            # Requesting ip and port of client, not server
            if filter == "" or fields == "":
                output = bytearray([int(x) for x in self.address.host.split('.')])
                output += utils.get_bytes_from_short_be(self.address.port)
                self.transport.write(bytes(output))
                logger.log(logging.DEBUG, "Responding with own IP and port...")
                logger.log(logging.DEBUG, utils.pretty_print_hex(output))
            else:
                self.find_server(query_game, filter, fields, max_servers, game_name, challenge)



        elif data[2] == '\x02': # Send message request
            dest_addr = '.'.join(["%d" % ord(x) for x in data[3:7]])
            dest_port = utils.get_short_be(data, 7) # What's the pythonic way to do this? unpack?
            dest = (dest_addr, dest_port)

            logger.log(logging.DEBUG, "Received send message request from %s:%s to %s:%d..." % (self.address.host, self.address.port, dest_addr, dest_port))
            logger.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

            self.forward_to_client = True
            self.forward_client = dest

        elif data[2] == '\x03': # Keep alive reply
            logger.log(logging.DEBUG, "Received keep alive from %s:%s..." % (self.address.host, self.address.port))

        else:
            logger.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s..." % (ord(data[2]), self.address.host, self.address.port))
            logger.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))
            logger.log(logging.DEBUG, utils.pretty_print_hex(data))
Пример #41
0
def handle_natneg(nn, recv_data, addr, socket):
    """Command: Unknown."""
    logger.log(logging.DEBUG, "Received unknown command %02x from %s:%d...",
               ord(recv_data[7]), *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
            ))

        elif recv_data[0] == '\x0a':  # Client Registered
            # Only sent to client, never received?
            self.log(logging.WARNING, address, session_id,
                     "NOT IMPLEMENTED! Received client registered"
                     " from %s:%s... %s",
                     address[0], address[1], recv_data[5:])

        else:
            self.log(logging.ERROR, address, session_id,
                     "Unknown request from %s:%s:",
                     address[0], address[1])
            self.log(logging.DEBUG, address, session_id,
                     "%s",
                     utils.pretty_print_hex(recv_data))

    def keepalive_check(self):
        # self.log(logging.DEBUG, None, session_id,
        #          "Keep alive check on %d sessions",
        #          len(self.sessions))

        pruned = []
        now = int(time.time())

        for session_id in self.sessions:
            delta = now - self.sessions[session_id].keepalive
            timeout = 61  # Remove clients that haven't responded in x seconds

            if delta < 0 or delta >= timeout:
                pruned.append(session_id)
Пример #43
0
    def rawDataReceived(self, data):
        try:
            # First 2 bytes are the packet size.
            #
            # Third byte is the command byte.
            # According to Openspy-Core:
            #   0x00 - Server list request
            #   0x01 - Server info request
            #   0x02 - Send message request
            #   0x03 - Keep alive reply
            #   0x04 - Map loop request (?)
            #   0x05 - Player search request
            #
            # For Tetris DS, at the very least 0x00 and 0x02 need to be
            # implemented.

            self.buffer += data

            while len(self.buffer) > 0:
                packet_len = utils.get_short(self.buffer, 0, True)
                packet = None

                if len(self.buffer) >= packet_len:
                    packet = self.buffer[:packet_len]
                    self.buffer = self.buffer[packet_len:]

                if packet is None:
                    # Don't have enough for the entire packet, break.
                    break

                if packet[2] == '\x00':  # Server list request
                    self.log(logging.DEBUG,
                             "Received server list request from %s:%s...",
                             self.address.host, self.address.port)

                    # This code is so... not python. The C programmer in me is
                    # coming out strong.
                    # TODO: Rewrite this section later?
                    idx = 3
                    list_version = ord(packet[idx])
                    idx += 1
                    encoding_version = ord(packet[idx])
                    idx += 1
                    game_version = utils.get_int(packet, idx)
                    idx += 4

                    query_game = utils.get_string(packet, idx)
                    idx += len(query_game) + 1
                    game_name = utils.get_string(packet, idx)
                    idx += len(game_name) + 1

                    challenge = ''.join(packet[idx:idx + 8])
                    idx += 8

                    filter = utils.get_string(packet, idx)
                    idx += len(filter) + 1
                    fields = utils.get_string(packet, idx)
                    idx += len(fields) + 1

                    options = utils.get_int(packet, idx, True)
                    idx += 4

                    source_ip = 0
                    max_servers = 0

                    NO_SERVER_LIST = 0x02
                    ALTERNATE_SOURCE_IP = 0x08
                    LIMIT_RESULT_COUNT = 0x80

                    send_ip = False
                    if (options & LIMIT_RESULT_COUNT):
                        max_servers = utils.get_int(packet, idx)
                    elif (options & ALTERNATE_SOURCE_IP):
                        source_ip = utils.get_ip(packet, idx)
                    elif (options & NO_SERVER_LIST):
                        send_ip = True

                    if '\\' in fields:
                        fields = [
                            x for x in fields.split('\\')
                            if x and not x.isspace()
                        ]

                    # print "%02x %02x %08x" % \
                    #       (list_version, encoding_version, game_version)
                    # print "%s" % query_game
                    # print "%s" % game_name
                    # print "%s" % challenge
                    # print "%s" % filter
                    # print "%s" % fields

                    # print "%08x" % options
                    # print "%d %08x" % (max_servers, source_ip)

                    self.log(
                        logging.DEBUG,
                        "list version: %02x / encoding version: %02x /"
                        " game version: %08x / query game: %s /"
                        " game name: %s / challenge: %s / filter: %s /"
                        " fields: %s / options: %08x / max servers: %d /"
                        " source ip: %08x" %
                        (list_version, encoding_version, game_version,
                         query_game, game_name, challenge, filter, fields,
                         options, max_servers, source_ip))

                    # Requesting ip and port of client, not server
                    if not filter and not fields or send_ip:
                        output = bytearray(
                            [int(x) for x in self.address.host.split('.')])
                        # Does this ever change?
                        output += utils.get_bytes_from_short(6500, True)

                        enc = gs_utils.EncTypeX()
                        output_enc = enc.encrypt(
                            self.secret_key_list[game_name], challenge, output)

                        self.transport.write(bytes(output_enc))

                        self.log(logging.DEBUG, "%s",
                                 "Responding with own IP and game port...")
                        self.log(logging.DEBUG, "%s",
                                 utils.pretty_print_hex(output))
                    else:
                        self.find_server(query_game, filter, fields,
                                         max_servers, game_name, challenge)

                elif packet[2] == '\x02':  # Send message request
                    packet_len = utils.get_short(packet, 0, True)
                    dest_addr = '.'.join(["%d" % ord(x) for x in packet[3:7]])
                    # What's the pythonic way to do this? unpack?
                    dest_port = utils.get_short(packet, 7, True)
                    dest = (dest_addr, dest_port)

                    self.log(
                        logging.DEBUG,
                        "Received send message request from %s:%s to"
                        " %s:%d... expecting %d byte packet.",
                        self.address.host, self.address.port, dest_addr,
                        dest_port, packet_len)
                    self.log(logging.DEBUG, "%s",
                             utils.pretty_print_hex(bytearray(packet)))

                    if packet_len == len(packet):
                        # Contains entire packet, send immediately.
                        self.forward_data_to_client(packet[9:], dest)
                    else:
                        self.log(logging.ERROR, "%s",
                                 "ERROR: Could not find entire packet.")

                elif packet[2] == '\x03':  # Keep alive reply
                    self.log(logging.DEBUG,
                             "Received keep alive from %s:%s...",
                             (self.address.host, self.address.port))

                else:
                    self.log(logging.DEBUG,
                             "Received unknown command (%02x) from %s:%s...",
                             ord(packet[2]), self.address.host,
                             self.address.port)
                    self.log(logging.DEBUG, "%s",
                             utils.pretty_print_hex(bytearray(packet)))
        except:
            self.log(logging.ERROR, "Unknown exception: %s",
                     traceback.format_exc())
    def wait_loop(self):
        while 1:
            recv_data, address = self.socket.recvfrom(2048)

            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            #
            # After some more packet inspection, it seems the format goes something like this:
            # - All server messages seem to always start with \xfe\xfd.
            # - The first byte from the client (or third byte from the server) is a command.
            # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
            # session-like ID because the number changes between connections. Copying the client's ID might be enough.
            #
            # The above was as guessed.
            # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
            # R1 contains the command to be sent to the server.
            # R2 contains a pointer to some unknown integer that gets written after the command.
            #
            # - Commands
            #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
            #
            #   CLIENT:
            #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
            #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
            #
            #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
            #           Data sent:
            #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
            #           2) localport\x00(local port)
            #           3) natneg (either 0 or 1)
            #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
            #           5) gamename\x00(game name)
            #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
            #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
            #
            #           if statechanged != 2:
            #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
            #
            #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
            #
            #       0x08 - Keep alive? Sent after 0x03
            #
            #       0x09 - Availability check
            #
            #   SERVER:
            #       0x01 - Unknown
            #           Data sent:
            #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
            #
            #       0x06 - Unknown
            #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
            #           seeing how the server can send the same IP information many times in a row. If the IP information has
            #           already been parsed then it doesn't waste time handling it.
            #
            #           After that is a "SBCM" section which is 0x14 bytes in total.
            #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
            #           Seems to contain IP address information.
            #
            #           The SBCM seems to contain a little information that must be parsed before.
            #           After the SBCM:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x01 - Found player?
            #               \x04 - Unknown
            #               (2 bytes) - Unknown. Port?
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #               \x00\x00\x00\x00 - Unknown but seems to get checked
            #
            #           Another SBCM, after a player has been found and attempting to start a game:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x05 - Connecting to player?
            #               \x00 - Unknown
            #               (2 bytes) - Unknown. Port? Same as before.
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #
            #       0x0a - Response to 0x01
            #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
            #           Has no other data besides the client ID.
            #
            #  - \xfd\xfc commands get passed directly between the other player(s)?
            #
            #
            # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
            # Use as reference.

            if recv_data[0] != '\x09':
                # Don't add a session if the client is trying to check if the game is available or not
                session_id = struct.unpack("<I", recv_data[1:5])[0]
                session_id_raw = recv_data[1:5]
                if session_id not in self.sessions:
                    # Found a new session, add to session list
                    self.sessions[session_id] = self.Session(address)
                    self.sessions[session_id].session = session_id
                    self.sessions[session_id].keepalive = int(time.time())

            # Handle commands
            if recv_data[0] == '\x00': # Query
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x01': # Challenge
                self.log(logging.DEBUG, address, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

                # Prepare the challenge sent from the server to be compared
                challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge)

                # Compare challenge
                client_challenge = recv_data[5:-1]
                if client_challenge == challenge:
                    # Challenge succeeded

                    # Send message back to client saying it was accepted
                    packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command
                    packet.extend(session_id_raw) # Get the session ID
                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1]))
                else:
                    # Failed the challenge, request another during the next heartbeat
                    self.sessions[session_id].sent_challenge = False

            elif recv_data[0] == '\x02': # Echo
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x03': # Heartbeat
                data = recv_data[5:]
                self.log(logging.DEBUG, address, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))

                # Parse information from heartbeat here
                d = data.rstrip('\0').split('\0')

                # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
                k = {}
                for i in range(0, len(d), 2):
                    #self.log(logging.DEBUG, address, "%s = %s" % (d[i], d[i+1]))
                    k[d[i]] = d[i+1]

                if "gamename" in k:
                    self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
                    #print "Got secret key %s for %s" % (self.sessions[session_id].secretkey, k['gamename'])

                if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                    # Get the player's id and then query the profile to figure out what console they are on.
                    # The endianness of some server data depends on the endianness of the console, so we must be able
                    # to account for that.
                    self.sessions[session_id].playerid = int(k['dwc_pid'])
                    profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)

                    if "console" in profile:
                        self.sessions[session_id].console = profile['console']


                if self.sessions[session_id].sent_challenge == False:
                    addr_hex =  ''.join(["%02X" % int(x) for x in address[0].split('.')])
                    port_hex = "%04X" % int(address[1])
                    server_challenge = utils.generate_random_str(8) + addr_hex + port_hex

                    self.sessions[session_id].challenge = server_challenge

                    packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command
                    packet.extend(session_id_raw) # Get the session ID
                    packet.extend(server_challenge)
                    packet.extend('\x00')

                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent challenge to %s:%s..." % (address[0], address[1]))

                    self.sessions[session_id].sent_challenge = True

                if 'publicip' in k and k['publicip'] == "0": #and k['dwc_hoststate'] == "2": # When dwc_hoststate == 2 then it doesn't send an IP, so calculate it ourselves
                    if self.sessions[session_id].console != 0:
                        k['publicip'] = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # Wii
                    else:
                        k['publicip'] = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # DS

                if "statechanged" in k:
                    if k['statechanged'] == "1": # Create server
                        #if k['publicport'] != "0" and k['publicip'] != "0":
                            # dwc_mtype controls what kind of server query we're looking for.
                            # dwc_mtype = 0 is used when looking for a matchmaking game.
                            # dwc_mtype = 1 is unknown.
                            # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too).
                            # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too).

                            # Some memory could be saved by clearing out any unwanted fields from k before sending.
                        self.server_manager.update_server_list(k['gamename'], session_id, k, self.sessions[session_id].console)._getvalue()

                        if session_id in self.sessions:
                            self.sessions[session_id].gamename = k['gamename']
                    elif k['statechanged'] == "2": # Close server
                        self.server_manager.delete_server(k['gamename'] , session_id)

                        if session_id in self.sessions:
                            self.sessions.pop(session_id)


            elif recv_data[0] == '\x04': # Add Error
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x05': # Echo Response
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x06': # Client Message
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x07': # Client Message Ack
                #self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
                self.log(logging.DEBUG, address, "Received client message ack from %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x08': # Keep Alive
                self.log(logging.DEBUG, address, "Received keep alive from %s:%s..." % (address[0], address[1]))
                self.sessions[session_id].keepalive = int(time.time())

            elif recv_data[0] == '\x09': # Available
                # Availability check only sent to *.available.gs.nintendowifi.net
                self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
                self.socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)

            elif recv_data[0] == '\x0a': # Client Registered
                # Only sent to client, never received?
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            else:
                self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
                self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
    def start(self):
        # Start natneg server
        address = ('0.0.0.0', 27901)  # accessible to outside connections (use this if you don't know what you're doing)

        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.bind(address)

        logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1]))

        while 1:
            recv_data, addr = s.recvfrom(2048)

            logger.log(logging.DEBUG, "Connection from %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            # Make sure it's a legal packet
            if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
                continue

            session_id = struct.unpack("<I", recv_data[8:12])[0]
            session_id_raw = recv_data[8:12]

            # Handle commands
            if recv_data[7] == '\x00':
                logger.log(logging.DEBUG, "Received initialization from %s:%s..." % (addr[0], addr[1]))

                output = bytearray(recv_data[0:14])
                output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea ]) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00
                output[7] = 0x01 # Initialization response
                s.sendto(output, addr)

                # Try to connect to the server
                gameid = utils.get_string(recv_data, 0x16)
                client_id = "%02x" % ord(recv_data[13])

                if gameid not in self.session_list:
                    self.session_list[gameid] = {}
                if session_id not in self.session_list[gameid]:
                    self.session_list[gameid][session_id] = {}
                if client_id not in self.session_list[gameid][session_id]:
                    self.session_list[gameid][session_id][client_id] = { 'connected': False, 'addr': '' }

                self.session_list[gameid][session_id][client_id]['addr'] = addr
                clients = len(self.session_list[gameid][session_id])

                for client in self.session_list[gameid][session_id]:
                    if self.session_list[gameid][session_id][client]['connected'] == False:
                        if client == client_id:
                            continue

                        # Send to requesting client
                        output = bytearray(recv_data[0:12])
                        output += bytearray([int(x) for x in self.session_list[gameid][session_id][client]['addr'][0].split('.')])
                        output += utils.get_bytes_from_short_be(self.session_list[gameid][session_id][client]['addr'][1])

                        output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
                        output[7] = 0x05
                        s.sendto(output, (self.session_list[gameid][session_id][client_id]['addr']))

                        logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[gameid][session_id][client_id]['addr'][0], self.session_list[gameid][session_id][client_id]['addr'][1]))
                        logger.log(logging.DEBUG, utils.pretty_print_hex(output))
                        logger.log(logging.DEBUG, "")

                        # Send to other client
                        output = bytearray(recv_data[0:12])
                        output += bytearray([int(x) for x in self.session_list[gameid][session_id][client_id]['addr'][0].split('.')])
                        output += utils.get_bytes_from_short_be(self.session_list[gameid][session_id][client_id]['addr'][1])

                        output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
                        output[7] = 0x05
                        s.sendto(output, (self.session_list[gameid][session_id][client]['addr']))

                        logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[gameid][session_id][client]['addr'][0], self.session_list[gameid][session_id][client]['addr'][1]))
                        logger.log(logging.DEBUG, utils.pretty_print_hex(output))
                        logger.log(logging.DEBUG, "")

            elif recv_data[7] == '\x06': # Was able to connect
                client_id = "%02x" % ord(recv_data[13])
                logger.log(logging.DEBUG, "Received connected command from %s:%s..." % (addr[0], addr[1]))

                if gameid not in self.session_list:
                    pass
                if session_id not in self.session_list[gameid]:
                    pass
                if client_id not in self.session_list[gameid][session_id]:
                    pass

                #self.session_list[gameid][session_id][client_id]['connected'] = True

            elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED!
                client_id = "%02x" % ord(recv_data[13])
                logger.log(logging.DEBUG, "Received address check command from %s:%s..." % (addr[0], addr[1]))

                output = bytearray(recv_data[0:15])
                output += bytearray([int(x) for x in addr[0].split('.')])
                output += utils.get_bytes_from_short_be(addr[1])
                output += bytearray(recv_data[len(output):])

                output[7] = 0x0b
                s.sendto(output, addr)

                logger.log(logging.DEBUG, "Sent address check response to %s:%d..." % (self.session_list[gameid][session_id][client]['addr'][0], self.session_list[gameid][session_id][client]['addr'][1]))
                logger.log(logging.DEBUG, utils.pretty_print_hex(output))

            elif recv_data[7] == '\x0d':
                client_id = "%02x" % ord(recv_data[13])
                logger.log(logging.DEBUG, "Received report command from %s:%s..." % (addr[0], addr[1]))
                logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

                output = bytearray(recv_data)
                output[7] = 0x0e # Report response
                s.sendto(recv_data, addr)

            else: # Was able to connect
                logger.log(logging.DEBUG, "Received unknown command %02x from %s:%s..." % (ord(recv_data[7]), addr[0], addr[1]))
def handle_natneg(nn, recv_data, addr, socket):
    """Command: Unknown."""
    logger.log(logging.DEBUG,
               "Received unknown command %02x from %s:%d...",
               ord(recv_data[7]), *addr)
    logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
def handle_natneg_init(nn, recv_data, addr, socket):
    """Command: 0x00 - NN_INIT.

    Send by the client to initialize the connection.

    Example:
    fd fc 1e 66 6a b2 03 00 3d f1 00 71 00 00 01 0a
    00 01 e2 00 00 6d 61 72 69 6f 6b 61 72 74 77 69
    69 00

    Description:
    fd fc 1e 66 6a b2 - NATNEG magic
    03                - NATNEG version
    00                - NATNEG record type
    3d f1 00 71       - Session id
    00                - Port type (between 0x00 and 0x03)
    00                - Client index (0x00 - Client,
                                      0x01 - Host)
    01                - Use game port
    0a 00 01 e2       - Local IP
    00 00             - Local port
    GAME_NAME 00      - Game name
    """
    logger.log(logging.DEBUG, "Received initialization from %s:%d...", *addr)

    session_id = utils.get_int(recv_data, 8)
    output = bytearray(recv_data[0:14])

    # Checked with Tetris DS, Mario Kart DS, and Metroid Prime
    # Hunters, and this seems to be the standard response to 0x00
    output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea])
    output[7] = 0x01  # Initialization response
    nn.write_queue.put((output, addr, socket))

    # Try to connect to the server
    gameid = utils.get_string(recv_data, 0x15)
    client_id = "%02x" % ord(recv_data[13])
    localaddr = utils.get_local_addr(recv_data, 15)

    nn.session_list \
        .setdefault(session_id, {}) \
        .setdefault(client_id,
                    {
                        'connected': False,
                        'addr': '',
                        'localaddr': None,
                        'serveraddr': None,
                        'gameid': None
                    })

    # In fact, it's a pointer
    client_id_session = nn.session_list[session_id][client_id]
    client_id_session['gameid'] = gameid
    client_id_session['addr'] = addr
    client_id_session['localaddr'] = localaddr

    for client in nn.session_list[session_id]:
        # Another pointer
        client_session = nn.session_list[session_id][client]
        if client_session['connected'] or client == client_id:
            continue

        # --- Send to requesting client
        # Get server info
        serveraddr = nn.get_server_addr(gameid, session_id, client)
        client_session['serveraddr'] = serveraddr
        logger.log(logging.DEBUG,
                   "Found server from local ip/port: %s from %d",
                   serveraddr, session_id)

        # Get public port
        if client_session['serveraddr'] is not None:
            publicport = int(client_session['serveraddr']['publicport'])
        else:
            publicport = \
                client_session['localaddr'][1] or \
                client_session['addr'][1]

        output = bytearray(recv_data[0:12])
        output += utils.get_bytes_from_ip_str(client_session['addr'][0])
        output += utils.get_bytes_from_short(publicport, True)

        # Unknown, always seems to be \x42\x00
        output += bytearray([0x42, 0x00])
        output[7] = 0x05  # NN_CONNECT
        nn.write_queue.put((output, client_id_session['addr'], socket))

        logger.log(logging.DEBUG,
                   "Sent connection request to %s:%d...",
                   *client_id_session['addr'])
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))

        # --- Send to other client
        # Get server info
        serveraddr = nn.get_server_addr(gameid, session_id, client_id)
        client_id_session['serveraddr'] = serveraddr
        logger.log(logging.DEBUG,
                   "Found server 2 from local ip/port: %s from %d",
                   serveraddr, session_id)

        # Get public port
        if client_id_session['serveraddr'] is not None:
            publicport = int(client_id_session['serveraddr']['publicport'])
        else:
            publicport = \
                client_id_session['localaddr'][1] or \
                client_id_session['addr'][1]

        output = bytearray(recv_data[0:12])
        output += utils.get_bytes_from_ip_str(client_id_session['addr'][0])
        output += utils.get_bytes_from_short(publicport, True)

        # Unknown, always seems to be \x42\x00
        output += bytearray([0x42, 0x00])
        output[7] = 0x05  # NN_CONNECT
        nn.write_queue.put((output, client_session['addr'], socket))

        logger.log(logging.DEBUG,
                   "Sent connection request to %s:%d...",
                   *client_session['addr'])
        logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
    def handle_packet(self, recv_data, addr):
        logger.log(logging.DEBUG, "Connection from %s:%d..." % (addr[0], addr[1]))
        logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

        # Make sure it's a legal packet
        if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
            return

        session_id = struct.unpack("<I", recv_data[8:12])[0]
        session_id_raw = recv_data[8:12]

        # Handle commands
        if recv_data[7] == '\x00':
            logger.log(logging.DEBUG, "Received initialization from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data[0:14])
            output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea ]) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00
            output[7] = 0x01 # Initialization response
            self.write_queue.put((output, addr))

            # Try to connect to the server
            gameid = utils.get_string(recv_data, 0x15)
            client_id = "%02x" % ord(recv_data[13])

            localip_raw = recv_data[15:19]
            localip_int_le = utils.get_int(recv_data, 15)
            localip_int_be = utils.get_int_be(recv_data, 15)
            localip = '.'.join(["%d" % ord(x) for x in localip_raw])
            localport_raw = recv_data[19:21]
            localport = utils.get_short_be(localport_raw, 0)
            localaddr = (localip, localport, localip_int_le, localip_int_be)

            if session_id not in self.session_list:
                self.session_list[session_id] = {}
            if client_id not in self.session_list[session_id]:
                self.session_list[session_id][client_id] = { 'connected': False, 'addr': '', 'localaddr': None, 'serveraddr': None, 'gameid': None }

            self.session_list[session_id][client_id]['gameid'] = gameid
            self.session_list[session_id][client_id]['addr'] = addr
            self.session_list[session_id][client_id]['localaddr'] = localaddr
            clients = len(self.session_list[session_id])

            for client in self.session_list[session_id]:
                if self.session_list[session_id][client]['connected'] == False: # and self.session_list[session_id][client]['localaddr'][1] != 0:
                    if client == client_id:
                        continue

                    #if self.session_list[session_id][client]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id, client)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(gameid, session_id, client)

                    self.session_list[session_id][client]['serveraddr'] = serveraddr
                    logger.log(logging.DEBUG, "Found server from local ip/port: %s from %d" % (serveraddr, session_id))

                    publicport = self.session_list[session_id][client]['addr'][1]
                    if self.session_list[session_id][client]['localaddr'][1] != 0:
                        publicport = self.session_list[session_id][client]['localaddr'][1]

                    if self.session_list[session_id][client]['serveraddr'] != None:
                        publicport = int(self.session_list[session_id][client]['serveraddr']['publicport'])

                    # Send to requesting client
                    output = bytearray(recv_data[0:12])
                    output += bytearray([int(x) for x in self.session_list[session_id][client]['addr'][0].split('.')])
                    output += utils.get_bytes_from_short_be(publicport)

                    output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #self.write_queue.put((output, (self.session_list[session_id][client_id]['addr'])))
                    self.write_queue.put((output, (self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1])))

                    logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

                    # Send to other client
                    #if self.session_list[session_id][client_id]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id, client_id)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(gameid, session_id, client)

                    self.session_list[session_id][client_id]['serveraddr'] = serveraddr
                    logger.log(logging.DEBUG, "Found server 2 from local ip/port: %s from %d" % (serveraddr, session_id))

                    publicport = self.session_list[session_id][client_id]['addr'][1]
                    if self.session_list[session_id][client_id]['localaddr'][1] != 0:
                        publicport = self.session_list[session_id][client_id]['localaddr'][1]

                    if self.session_list[session_id][client_id]['serveraddr'] != None:
                        publicport = int(self.session_list[session_id][client_id]['serveraddr']['publicport'])

                    output = bytearray(recv_data[0:12])
                    output += bytearray([int(x) for x in self.session_list[session_id][client_id]['addr'][0].split('.')])
                    output += utils.get_bytes_from_short_be(publicport)

                    output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #self.write_queue.put((output, (self.session_list[session_id][client]['addr'])))
                    self.write_queue.put((output, (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1])))

                    logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x06': # Was able to connect
            client_id = "%02x" % ord(recv_data[13])
            logger.log(logging.DEBUG, "Received connected command from %s:%s..." % (addr[0], addr[1]))

            if session_id not in self.session_list:
                return
            if client_id not in self.session_list[session_id]:
                return

            self.session_list[session_id][client_id]['connected'] = True

        elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED!
            client_id = "%02x" % ord(recv_data[13])
            logger.log(logging.DEBUG, "Received address check command from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data[0:15])
            output += bytearray([int(x) for x in addr[0].split('.')])
            output += utils.get_bytes_from_short_be(addr[1])
            output += bytearray(recv_data[len(output):])

            output[7] = 0x0b
            self.write_queue.put((output, addr))

            logger.log(logging.DEBUG, "Sent address check response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0c': # Natify
            port_type = "%02x" % ord(recv_data[12])
            logger.log(logging.DEBUG, "Received natify command from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data)
            output[7] = 0x02 # ERT Test
            self.write_queue.put((output, addr))

            logger.log(logging.DEBUG, "Sent natify response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0d':
            client_id = "%02x" % ord(recv_data[13])
            logger.log(logging.DEBUG, "Received report command from %s:%s..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            output = bytearray(recv_data)
            output[7] = 0x0e # Report response
            self.write_queue.put((recv_data, addr))

        else: # Was able to connect
            logger.log(logging.DEBUG, "Received unknown command %02x from %s:%s..." % (ord(recv_data[7]), addr[0], addr[1]))
Пример #49
0
    def rawDataReceived(self, data):
        # First 2 bytes are the packet size.
        #
        # Third byte is the command byte.
        # According to Openspy-Core:
        #   0x00 - Server list request
        #   0x01 - Server info request
        #   0x02 - Send message request
        #   0x03 - Keep alive reply
        #   0x04 - Map loop request (?)
        #   0x05 - Player search request
        #
        # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented.
        if self.forward_to_client:
            if self.forward_packet == None:
                self.forward_packet = data
            else:
                self.forward_packet += data

            if self.header_length + len(self.forward_packet) >= self.expected_packet_length:
                # Is it possible that multiple packets will need to be waited for?
                # Is it possible that more data will be in the last packet than expected?
                self.forward_data_to_client(self.forward_packet, self.forward_client)

                self.forward_to_client = False
                self.forward_client = ()
                self.header_length = 0
                self.expected_packet_length = 0
                self.forward_packet = None
            return

        if data[2] == '\x00': # Server list request
            self.log(logging.DEBUG, "Received server list request from %s:%s..." % (self.address.host, self.address.port))

            # This code is so... not python. The C programmer in me is coming out strong.
            # TODO: Rewrite this section later?
            idx = 3
            list_version = ord(data[idx])
            idx += 1
            encoding_version = ord(data[idx])
            idx += 1
            game_version = utils.get_int(data, idx)
            idx += 4

            query_game = utils.get_string(data, idx)
            idx += len(query_game) + 1
            game_name = utils.get_string(data, idx)
            idx += len(game_name) + 1

            challenge = data[idx:idx+8]
            idx += 8

            filter = utils.get_string(data, idx)
            idx += len(filter) + 1
            fields = utils.get_string(data, idx)
            idx += len(fields) + 1

            options = utils.get_int_be(data, idx)
            idx += 4

            source_ip = 0
            max_servers = 0

            NO_SERVER_LIST = 0x02
            ALTERNATE_SOURCE_IP = 0x08
            LIMIT_RESULT_COUNT = 0x80

            send_ip = False
            if (options & LIMIT_RESULT_COUNT):
                max_servers = utils.get_int(data, idx)
            elif (options & ALTERNATE_SOURCE_IP):
                source_ip = utils.get_int(data, idx)
            elif (options & NO_SERVER_LIST):
                send_ip = True

            if '\\' in fields:
                fields = [x for x in fields.split('\\') if x and not x.isspace()]

            #print "%02x %02x %08x" % (list_version, encoding_version, game_version)
            #print "%s" % query_game
            #print "%s" % game_name
            #print "%s" % challenge
            #print "%s" % filter
            #print "%s" % fields

            #print "%08x" % options
            #print "%d %08x" % (max_servers, source_ip)

            self.log(logging.DEBUG, "list version: %02x / encoding version: %02x / game version: %08x / query game: %s / game name: %s / challenge: %s / filter: %s / fields: %s / options: %08x / max servers: %d / source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip))

            # Requesting ip and port of client, not server
            if filter == "" or fields == "" or send_ip == True:
                output = bytearray([int(x) for x in self.address.host.split('.')])
                output += utils.get_bytes_from_short_be(6500) # Does this ever change?

                enc = gs_utils.EncTypeX()
                output_enc = enc.encrypt(self.secret_key_list[game_name], challenge, output)

                self.transport.write(bytes(output_enc))

                self.log(logging.DEBUG, "Responding with own IP and game port...")
                self.log(logging.DEBUG, utils.pretty_print_hex(output))
            else:
                self.find_server(query_game, filter, fields, max_servers, game_name, challenge)



        elif data[2] == '\x02': # Send message request
            packet_len = utils.get_short_be(data, 0)
            dest_addr = '.'.join(["%d" % ord(x) for x in data[3:7]])
            dest_port = utils.get_short_be(data, 7) # What's the pythonic way to do this? unpack?
            dest = (dest_addr, dest_port)

            self.log(logging.DEBUG, "Received send message request from %s:%s to %s:%d... expecting %d byte packet." % (self.address.host, self.address.port, dest_addr, dest_port, packet_len))
            self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))

            if packet_len == len(data):
                # Contains entire packet, send immediately.
                self.forward_data_to_client(data[3:], dest)
                
                self.forward_to_client = False
                self.forward_client = ()
                self.header_length = 0
                self.expected_packet_length = 0
                self.forward_packet = None
            else:
                self.forward_to_client = True
                self.forward_client = dest
                self.header_length = len(data)
                self.expected_packet_length = packet_len

        elif data[2] == '\x03': # Keep alive reply
            self.log(logging.DEBUG, "Received keep alive from %s:%s..." % (self.address.host, self.address.port))

        else:
            self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s..." % (ord(data[2]), self.address.host, self.address.port))
            self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data)))
            self.log(logging.DEBUG, utils.pretty_print_hex(data))
        elif recv_data[0] == '\x08': # Keep Alive
            self.log(logging.DEBUG, address, session_id, "Received keep alive from %s:%s..." % (address[0], address[1]))
            self.sessions[session_id].keepalive = int(time.time())

        elif recv_data[0] == '\x09': # Available
            # Availability check only sent to *.available.gs.nintendowifi.net
            self.log(logging.DEBUG, address, session_id, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
            self.write_queue.put((bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address))

        elif recv_data[0] == '\x0a': # Client Registered
            # Only sent to client, never received?
            self.log(logging.WARNING, address, session_id, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

        else:
            self.log(logging.ERROR, address, session_id, "Unknown request from %s:%s:" % (address[0], address[1]))
            self.log(logging.DEBUG, address, session_id, utils.pretty_print_hex(recv_data))

    def keepalive_check(self):
        #self.log(logging.DEBUG, None, session_id, "Keep alive check on %d sessions" % (len(self.sessions)))

        pruned = []
        now = int(time.time())

        for session_id in self.sessions:
            delta = now - self.sessions[session_id].keepalive
            timeout = 61 # Remove clients that haven't responded in x seconds

            if delta < 0 or delta >= timeout:
                pruned.append(session_id)
                self.server_manager.delete_server(self.sessions[session_id].gamename, self.sessions[session_id].session)
                self.log(logging.DEBUG, None, session_id, "Keep alive check removed %s:%s for game %s. Client hasn't responded in %d seconds." % (self.sessions[session_id].address[0], self.sessions[session_id].address[1], self.sessions[session_id].gamename, delta))
    def handle_packet(self, recv_data, addr):
        """Handle NATNEG.

        TODO: Pointer to methods for recv_data[7]."""
        logger.log(logging.DEBUG,
                   "Connection from %s:%d...",
                   addr[0], addr[1])
        logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

        # Make sure it's a legal packet
        if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
            return

        session_id = struct.unpack("<I", recv_data[8:12])[0]
        session_id_raw = recv_data[8:12]

        # Handle commands
        if recv_data[7] == '\x00':
            logger.log(logging.DEBUG,
                       "Received initialization from %s:%s...",
                       addr[0], addr[1])

            output = bytearray(recv_data[0:14])
            # Checked with Tetris DS, Mario Kart DS, and Metroid Prime
            # Hunters, and this seems to be the standard response to 0x00
            output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea])
            output[7] = 0x01  # Initialization response
            self.write_queue.put((output, addr))

            # Try to connect to the server
            gameid = utils.get_string(recv_data, 0x15)
            client_id = "%02x" % ord(recv_data[13])

            localip_raw = recv_data[15:19]
            localip_int_le = utils.get_ip(recv_data, 15)
            localip_int_be = utils.get_ip(recv_data, 15, True)
            localip = '.'.join(["%d" % ord(x) for x in localip_raw])
            localport_raw = recv_data[19:21]
            localport = utils.get_short(localport_raw, 0, True)
            localaddr = (localip, localport, localip_int_le, localip_int_be)

            self.session_list \
                .setdefault(session_id, {}) \
                .setdefault(client_id,
                            {
                                'connected': False,
                                'addr': '',
                                'localaddr': None,
                                'serveraddr': None,
                                'gameid': None
                            })
            # In fact, it's a pointer (cf. shallow copy)
            client_id_session = self.session_list[session_id][client_id]

            client_id_session['gameid'] = gameid
            client_id_session['addr'] = addr
            client_id_session['localaddr'] = localaddr
            clients = len(self.session_list[session_id])  # Unused?

            for client in self.session_list[session_id]:
                # Another shallow copy
                client_session = self.session_list[session_id][client]
                if client_session['connected'] or client == client_id:
                    continue

                # if client_session['serveraddr'] \
                #     is None:
                serveraddr = self.get_server_info(gameid, session_id, client)
                if serveraddr is None:
                    serveraddr = self.get_server_info_alt(
                        gameid, session_id, client
                    )

                client_session['serveraddr'] = serveraddr
                logger.log(logging.DEBUG,
                           "Found server from local ip/port: %s from %d",
                           serveraddr, session_id)

                publicport = client_session['addr'][1]
                if client_session['localaddr'][1]:
                    publicport = client_session['localaddr'][1]

                if client_session['serveraddr'] is not None:
                    publicport = int(
                        client_session['serveraddr']['publicport']
                    )

                # Send to requesting client
                output = bytearray(recv_data[0:12])
                output += bytearray([
                    int(x) for x in client_session['addr'][0].split('.')
                ])
                output += utils.get_bytes_from_short(publicport, True)

                # Unknown, always seems to be \x42\x00
                output += bytearray([0x42, 0x00])
                output[7] = 0x05
                # self.write_queue.put((
                #     output,
                #     (client_id_session['addr'])
                # ))
                self.write_queue.put((
                    output,
                    (client_id_session['addr'][0],
                     client_id_session['addr'][1])
                ))

                logger.log(logging.DEBUG,
                           "Sent connection request to %s:%d...",
                           client_id_session['addr'][0],
                           client_id_session['addr'][1])
                logger.log(logging.DEBUG, '%s', utils.pretty_print_hex(output))

                # Send to other client
                # if client_id_session['serveraddr'] is None:
                serveraddr = self.get_server_info(
                    gameid, session_id, client_id
                )
                if serveraddr is None:
                    serveraddr = self.get_server_info_alt(
                        gameid, session_id, client_id
                    )

                client_id_session['serveraddr'] = serveraddr
                logger.log(logging.DEBUG,
                           "Found server 2 from local ip/port: %s from %d",
                           serveraddr, session_id)

                publicport = client_id_session['addr'][1]
                if client_id_session['localaddr'][1]:
                    publicport = client_id_session['localaddr'][1]

                if client_id_session['serveraddr'] is not None:
                    publicport = int(
                        client_id_session['serveraddr']['publicport']
                    )

                output = bytearray(recv_data[0:12])
                output += bytearray(
                    [int(x) for x in client_id_session['addr'][0].split('.')]
                )
                output += utils.get_bytes_from_short(publicport, True)

                # Unknown, always seems to be \x42\x00
                output += bytearray([0x42, 0x00])
                output[7] = 0x05
                # self.write_queue.put((output, (client_session['addr'])))
                self.write_queue.put((output, (client_session['addr'][0],
                                               client_session['addr'][1])))

                logger.log(logging.DEBUG,
                           "Sent connection request to %s:%d...",
                           client_session['addr'][0],
                           client_session['addr'][1])
                logger.log(logging.DEBUG,
                           '%s',
                           utils.pretty_print_hex(output))

        elif recv_data[7] == '\x06':  # Was able to connect
            client_id = "%02x" % ord(recv_data[13])
            logger.log(logging.DEBUG,
                       "Received connected command from %s:%s...",
                       addr[0], addr[1])

            if session_id in self.session_list and \
               client_id in self.session_list[session_id]:
                self.session_list[session_id][client_id]['connected'] = True

        elif recv_data[7] == '\x0a':  # Address check. Note: UNTESTED!
            client_id = "%02x" % ord(recv_data[13])
            logger.log(logging.DEBUG,
                       "Received address check command from %s:%s...",
                       addr[0], addr[1])

            output = bytearray(recv_data[0:15])
            output += bytearray([int(x) for x in addr[0].split('.')])
            output += utils.get_bytes_from_short(addr[1], True)
            output += bytearray(recv_data[len(output):])

            output[7] = 0x0b
            self.write_queue.put((output, addr))

            logger.log(logging.DEBUG,
                       "Sent address check response to %s:%d...",
                       addr[0], addr[1])
            logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0c':  # Natify
            port_type = "%02x" % ord(recv_data[12])
            logger.log(logging.DEBUG,
                       "Received natify command from %s:%s...",
                       addr[0], addr[1])

            output = bytearray(recv_data)
            output[7] = 0x02  # ERT Test
            self.write_queue.put((output, addr))

            logger.log(logging.DEBUG,
                       "Sent natify response to %s:%d...",
                       addr[0], addr[1])
            logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0d':  # Report
            logger.log(logging.DEBUG,
                       "Received report command from %s:%s...",
                       addr[0], addr[1])
            logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

            # Report response
            output = bytearray(recv_data[:21])
            output[7] = 0x0e  # Report response
            output[14] = 0  # Clear byte to match real server's response
            self.write_queue.put((output, addr))

        elif recv_data[7] == '\x0f':
            # Natneg v4 command thanks to Pipian.
            # Only seems to be used in very few DS games (namely,
            # Pokemon Black/White/Black 2/White 2).
            logger.log(logging.DEBUG,
                       "Received pre-init command from %s:%s...",
                       addr[0], addr[1])
            logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))

            session = utils.get_int(recv_data[-4:], 0)

            # Report response
            output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0])
            output[7] = 0x10  # Pre-init response

            if not session:
                # What's the correct behavior when session == 0?
                output[13] = 2
            elif session in self.natneg_preinit_session:
                # Should this be sent to both clients or just the one that
                # connected most recently?
                # I can't tell from a one sided packet capture of Pokemon.
                # For the time being, send to both clients just in case.
                output[13] = 2
                self.write_queue.put((output,
                                      self.natneg_preinit_session[session]))

                output[12] = (1, 0)[output[12]]  # Swap the index
                del self.natneg_preinit_session[session]
            else:
                output[13] = 0
                self.natneg_preinit_session[session] = addr

            self.write_queue.put((output, addr))

        else:  # Was able to connect
            logger.log(logging.DEBUG,
                       "Received unknown command %02x from %s:%s...",
                       ord(recv_data[7]), addr[0], addr[1])
            self.write_queue.put(
                (bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00,
                            0x00]), address))

        elif recv_data[0] == '\x0a':  # Client Registered
            # Only sent to client, never received?
            self.log(
                logging.WARNING, address, session_id,
                "NOT IMPLEMENTED! Received client registered from %s:%s... %s"
                % (address[0], address[1], recv_data[5:]))

        else:
            self.log(logging.ERROR, address, session_id,
                     "Unknown request from %s:%s:" % (address[0], address[1]))
            self.log(logging.DEBUG, address, session_id,
                     utils.pretty_print_hex(recv_data))

    def keepalive_check(self):
        #self.log(logging.DEBUG, None, session_id, "Keep alive check on %d sessions" % (len(self.sessions)))

        pruned = []
        now = int(time.time())

        for session_id in self.sessions:
            delta = now - self.sessions[session_id].keepalive
            timeout = 61  # Remove clients that haven't responded in x seconds

            if delta < 0 or delta >= timeout:
                pruned.append(session_id)
                self.server_manager.delete_server(
                    self.sessions[session_id].gamename,
    def wait_loop(self):
        while 1:
            recv_data, address = self.socket.recvfrom(2048)

            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            #
            # After some more packet inspection, it seems the format goes something like this:
            # - All server messages seem to always start with \xfe\xfd.
            # - The first byte from the client (or third byte from the server) is a command.
            # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
            # session-like ID because the number changes between connections. Copying the client's ID might be enough.
            #
            # The above was as guessed.
            # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
            # R1 contains the command to be sent to the server.
            # R2 contains a pointer to some unknown integer that gets written after the command.
            #
            # - Commands
            #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
            #
            #   CLIENT:
            #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
            #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
            #
            #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
            #           Data sent:
            #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
            #           2) localport\x00(local port)
            #           3) natneg (either 0 or 1)
            #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
            #           5) gamename\x00(game name)
            #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
            #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
            #
            #           if statechanged != 2:
            #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
            #
            #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
            #
            #       0x08 - Keep alive? Sent after 0x03
            #
            #       0x09 - Availability check
            #
            #   SERVER:
            #       0x01 - Unknown
            #           Data sent:
            #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
            #
            #       0x06 - Unknown
            #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
            #           seeing how the server can send the same IP information many times in a row. If the IP information has
            #           already been parsed then it doesn't waste time handling it.
            #
            #           After that is a "SBCM" section which is 0x14 bytes in total.
            #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
            #           Seems to contain IP address information.
            #
            #           The SBCM seems to contain a little information that must be parsed before.
            #           After the SBCM:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x01 - Found player?
            #               \x04 - Unknown
            #               (2 bytes) - Unknown. Port?
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #               \x00\x00\x00\x00 - Unknown but seems to get checked
            #
            #           Another SBCM, after a player has been found and attempting to start a game:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x05 - Connecting to player?
            #               \x00 - Unknown
            #               (2 bytes) - Unknown. Port? Same as before.
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #
            #       0x0a - Response to 0x01
            #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
            #           Has no other data besides the client ID.
            #
            #  - \xfd\xfc commands get passed directly between the other player(s)?
            #
            #
            # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
            # Use as reference.

            session_id = struct.unpack("<I", recv_data[1:5])[0]
            session_id_raw = recv_data[1:5]
            if session_id not in self.sessions:
                # Found a new session, add to session list
                self.sessions[session_id] = self.Session(address)

            # Handle commands
            if recv_data[0] == '\x00': # Query
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x01': # Challenge
                self.log(logging.DEBUG, address, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

                # Prepare the challenge sent from the server to be compared
                challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge)

                # Compare challenge
                client_challenge = recv_data[5:-1]
                if client_challenge == challenge:
                    # Challenge succeeded
                    self.sessions[session_id].sent_challenge = True

                    # Handle successful challenge stuff here
                    packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command
                    packet.extend(session_id_raw) # Get the session ID
                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x02': # Echo
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x03': # Heartbeat
                data = recv_data[5:]
                self.log(logging.DEBUG, address, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))

                # Parse information from heartbeat here
                d = data.rstrip('\0').split('\0')

                # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
                k = {}
                for i in range(0, len(d), 2):
                    self.log(logging.DEBUG, address, "%s = %s" % (d[i], d[i+1]))
                    k[d[i]] = d[i+1]

                if "gamename" in k:
                    self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
                    #print "Got secret key %s for %s" % (self.sessions[session_id].secretkey, k['gamename'])

                if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                    # Get the player's id and then query the profile to figure out what console they are on.
                    # The endianness of some server data depends on the endianness of the console, so we must be able
                    # to account for that.
                    self.sessions[session_id].playerid = int(k['dwc_pid'])
                    profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)

                    if "console" in profile:
                        self.sessions[session_id].console = profile['console']


                if self.sessions[session_id].sent_challenge == False:
                    addr_hex =  ''.join(["%02X" % int(x) for x in address[0].split('.')])
                    port_hex = "%04X" % int(address[1])
                    server_challenge = utils.generate_random_str(8) + addr_hex + port_hex

                    self.sessions[session_id].challenge = server_challenge

                    packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command
                    packet.extend(session_id_raw) # Get the session ID
                    packet.extend(server_challenge)
                    packet.extend('\x00')

                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent challenge to %s:%s..." % (address[0], address[1]))

                if "statechanged" in k:
                    if k['statechanged'] == "1": # Create server
                        if k['publicport'] != "0" and k['publicip'] != "0" and k['maxplayers'] != "0":
                            # dwc_mtype controls what kind of server query we're looking for.
                            # dwc_mtype = 0 is used when looking for a matchmaking game.
                            # dwc_mtype = 1 is unknown.
                            # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too).
                            # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too).

                            # Some memory could be saved by clearing out any unwanted fields from k before sending.
                            self.server_manager.update_server_list(k['gamename'] , session_id, k, self.sessions[session_id].console)
                    elif k['statechanged'] == "2": # Close server
                        self.server_manager.delete_server(k['gamename'] , session_id)
                        #self.sessions.pop(session_id)


            elif recv_data[0] == '\x04': # Add Error
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x05': # Echo Response
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x06': # Client Message
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x07': # Client Message Ack
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x08': # Keep Alive
                self.log(logging.DEBUG, address, "Received keep alive from %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x09': # Available
                # Availability check only sent to *.available.gs.nintendowifi.net
                self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
                self.socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)

            elif recv_data[0] == '\x0a': # Client Registered
                # Only sent to client, never received?
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            else:
                self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
                self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
    def start(self):
        # Start natneg server
        address = (
            '0.0.0.0', 27901
        )  # accessible to outside connections (use this if you don't know what you're doing)

        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.bind(address)

        logger.log(
            logging.INFO,
            "Server is now listening on %s:%s..." % (address[0], address[1]))

        while 1:
            recv_data, addr = s.recvfrom(2048)

            logger.log(logging.DEBUG,
                       "Connection from %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            # Make sure it's a legal packet
            if recv_data[0:6] != bytearray(
                [0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
                continue

            session_id = struct.unpack("<I", recv_data[8:12])[0]
            session_id_raw = recv_data[8:12]

            # Handle commands
            if recv_data[7] == '\x00':
                logger.log(
                    logging.DEBUG, "Received initialization from %s:%s..." %
                    (addr[0], addr[1]))

                output = bytearray(recv_data[0:14])
                output += bytearray(
                    [0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]
                )  # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00
                output[7] = 0x01  # Initialization response
                s.sendto(output, addr)

                # Try to connect to the server
                gameid = utils.get_string(recv_data, 0x16)
                client_id = "%02x" % ord(recv_data[13])

                localip_raw = recv_data[15:19]
                localip = '.'.join(["%d" % ord(x) for x in localip_raw])
                localport_raw = recv_data[19:21]
                localport = utils.get_short_be(localport_raw, 0)
                localaddr = (localip, localport)

                if gameid not in self.session_list:
                    self.session_list[gameid] = {}
                if session_id not in self.session_list[gameid]:
                    self.session_list[gameid][session_id] = {}
                if client_id not in self.session_list[gameid][session_id]:
                    self.session_list[gameid][session_id][client_id] = {
                        'connected': False,
                        'addr': '',
                        'localaddr': None,
                        'serveraddr': None,
                        'gameid': None
                    }

                self.session_list[gameid][session_id][client_id][
                    'gameid'] = utils.get_string(recv_data[21:], 0)
                self.session_list[gameid][session_id][client_id]['addr'] = addr
                self.session_list[gameid][session_id][client_id][
                    'localaddr'] = localaddr
                clients = len(self.session_list[gameid][session_id])

                for client in self.session_list[gameid][session_id]:
                    if self.session_list[gameid][session_id][client][
                            'connected'] == False and self.session_list[gameid][
                                session_id][client]['localaddr'][1] != 0:
                        if client == client_id:
                            continue

                        if self.session_list[gameid][session_id][client][
                                'serveraddr'] == None:
                            serveraddr = self.get_server_info(
                                gameid, session_id, client)
                            self.session_list[gameid][session_id][client][
                                'serveraddr'] = serveraddr
                            logger.log(
                                logging.DEBUG,
                                "Found server from local ip/port: %s" %
                                serveraddr)

                        publicport = 0
                        if self.session_list[gameid][session_id][client][
                                'serveraddr'] != None:
                            publicport = int(
                                self.session_list[gameid][session_id][client]
                                ['serveraddr']['publicport'])

                        # Send to requesting client
                        output = bytearray(recv_data[0:12])
                        output += bytearray([
                            int(x) for x in self.session_list[gameid]
                            [session_id][client]['addr'][0].split('.')
                        ])
                        output += utils.get_bytes_from_short_be(publicport)

                        output += bytearray(
                            [0x42,
                             0x00])  # Unknown, always seems to be \x42\x00
                        output[7] = 0x05
                        #s.sendto(output, (self.session_list[gameid][session_id][client_id]['addr']))
                        s.sendto(
                            output,
                            (self.session_list[gameid][session_id][client_id]
                             ['addr'][0], self.session_list[gameid][session_id]
                             [client_id]['addr'][1]))

                        logger.log(
                            logging.DEBUG,
                            "Sent connection request to %s:%d..." %
                            (self.session_list[gameid][session_id][client_id]
                             ['addr'][0], self.session_list[gameid][session_id]
                             [client_id]['addr'][1]))
                        logger.log(logging.DEBUG,
                                   utils.pretty_print_hex(output))

                        # Send to other client
                        if self.session_list[gameid][session_id][client_id][
                                'serveraddr'] == None:
                            serveraddr = self.get_server_info(
                                gameid, session_id, client_id)
                            self.session_list[gameid][session_id][client_id][
                                'serveraddr'] = serveraddr
                            logger.log(
                                logging.DEBUG,
                                "Found server 2 from local ip/port: %s" %
                                serveraddr)

                        publicport = 0
                        if self.session_list[gameid][session_id][client_id][
                                'serveraddr'] != None:
                            publicport = int(
                                self.session_list[gameid][session_id]
                                [client_id]['serveraddr']['publicport'])

                        output = bytearray(recv_data[0:12])
                        output += bytearray([
                            int(x) for x in self.session_list[gameid]
                            [session_id][client_id]['addr'][0].split('.')
                        ])
                        output += utils.get_bytes_from_short_be(publicport)

                        output += bytearray(
                            [0x42,
                             0x00])  # Unknown, always seems to be \x42\x00
                        output[7] = 0x05
                        #s.sendto(output, (self.session_list[gameid][session_id][client]['addr']))
                        s.sendto(output,
                                 (self.session_list[gameid][session_id][client]
                                  ['addr'][0], self.session_list[gameid]
                                  [session_id][client]['addr'][1]))

                        logger.log(
                            logging.DEBUG,
                            "Sent connection request to %s:%d..." %
                            (self.session_list[gameid][session_id][client]
                             ['addr'][0], self.session_list[gameid][session_id]
                             [client]['addr'][1]))
                        logger.log(logging.DEBUG,
                                   utils.pretty_print_hex(output))

            elif recv_data[7] == '\x06':  # Was able to connect
                client_id = "%02x" % ord(recv_data[13])
                logger.log(
                    logging.DEBUG, "Received connected command from %s:%s..." %
                    (addr[0], addr[1]))

                if gameid not in self.session_list:
                    pass
                if session_id not in self.session_list[gameid]:
                    pass
                if client_id not in self.session_list[gameid][session_id]:
                    pass

                self.session_list[gameid][session_id][client_id][
                    'connected'] = True

            elif recv_data[7] == '\x0a':  # Address check. Note: UNTESTED!
                client_id = "%02x" % ord(recv_data[13])
                logger.log(
                    logging.DEBUG,
                    "Received address check command from %s:%s..." %
                    (addr[0], addr[1]))

                output = bytearray(recv_data[0:15])
                output += bytearray([int(x) for x in addr[0].split('.')])
                output += utils.get_bytes_from_short_be(addr[1])
                output += bytearray(recv_data[len(output):])

                output[7] = 0x0b
                s.sendto(output, addr)

                logger.log(
                    logging.DEBUG, "Sent address check response to %s:%d..." %
                    (self.session_list[gameid][session_id][client]['addr'][0],
                     self.session_list[gameid][session_id][client]['addr'][1]))
                logger.log(logging.DEBUG, utils.pretty_print_hex(output))

            elif recv_data[7] == '\x0d':
                client_id = "%02x" % ord(recv_data[13])
                logger.log(
                    logging.DEBUG, "Received report command from %s:%s..." %
                    (addr[0], addr[1]))
                logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

                output = bytearray(recv_data)
                output[7] = 0x0e  # Report response
                s.sendto(recv_data, addr)

            else:  # Was able to connect
                logger.log(
                    logging.DEBUG,
                    "Received unknown command %02x from %s:%s..." %
                    (ord(recv_data[7]), addr[0], addr[1]))
Пример #55
0
    def forward_data_to_client(self, data, forward_client):
        # Find session id of server
        # Iterate through the list of servers sent to the client and match by
        # IP and port. Is there a better way to determine this information?
        if forward_client is None or len(forward_client) != 2:
            return

        server, ip = self.find_server_in_cache(forward_client[0],
                                               forward_client[1], self.console)

        if server is None:
            if self.console == 0:
                server, ip = self.find_server_in_cache(forward_client[0],
                                                       forward_client[1],
                                                       1)  # Try Wii
            elif self.console == 1:
                server, ip = self.find_server_in_cache(forward_client[0],
                                                       forward_client[1],
                                                       0)  # Try DS

        self.log(logging.DEBUG, "find_server_in_cache returned: %s", server)
        self.log(logging.DEBUG, "Trying to send message to %s:%d...",
                 forward_client[0], forward_client[1])
        self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(data)))

        if server is None:
            return

        self.log(logging.DEBUG, "%s %s", ip, server['publicip'])
        if server['publicip'] == ip and \
           server['publicport'] == str(forward_client[1]):
            if forward_client[1] == 0 and 'localport' in server:
                # No public port returned from client, try contacting on
                # the local port.
                forward_client = (forward_client[0], int(server['localport']))

            # Send command to server to get it to connect to natneg
            # Quick and lazy way to get a random 32bit integer. Replace with
            # something else later
            cookie = int(utils.generate_random_hex_str(8), 16)

            # if (len(data) == 24 and bytearray(data)[0:10] == \
            #     bytearray([0x53, 0x42, 0x43, 0x4d, 0x03,
            #                0x00, 0x00, 0x00, 0x01, 0x04])) or \
            #     (len(data) == 40 and bytearray(data)[0:10] == \
            #                          bytearray([0x53, 0x42, 0x43, 0x4d,
            #                                     0x0b, 0x00, 0x00, 0x00,
            #                                     0x01, 0x04])):
            if self.own_server is None and len(data) >= 16 and \
               bytearray(data)[0:4] in (bytearray([0xbb, 0x49, 0xcc, 0x4d]),
                                        bytearray([0x53, 0x42, 0x43, 0x4d])):
                # Is the endianness the same between the DS and Wii here?
                # It seems so but I'm not positive.
                # Note to self: Port is little endian here.
                self_port = utils.get_short(bytearray(data[10:12]), 0, False)
                self_ip = '.'.join(["%d" % x for x in bytearray(data[12:16])])

                self.own_server, _ = self.find_server_in_cache(
                    self_ip, self_port, self.console)

                if self.own_server is None:
                    if self.console == 0:
                        # Try Wii
                        self.own_server, _ = self.find_server_in_cache(
                            self_ip, self_port, 1)
                    elif self.console == 1:
                        # Try DS
                        self.own_server, _ = self.find_server_in_cache(
                            self_ip, self_port, 0)

                if self.own_server is None:
                    self.log(logging.DEBUG, "Could not find own server: %s:%d",
                             self_ip, self_port)
                else:
                    self.log(logging.DEBUG, "Found own server: %s",
                             self.own_server)

            elif len(data) == 10 and \
                    bytearray(data)[0:6] == \
                    bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
                natneg_session = utils.get_int_signed(data, 6)
                self.log(logging.DEBUG, "Adding %d to natneg server list: %s",
                         natneg_session, server)
                # Store info in backend so we can get it later in natneg
                self.server_manager.add_natneg_server(natneg_session, server)

                if self.own_server is not None:
                    self.log(logging.DEBUG,
                             "Adding %d to natneg server list: %s (self)",
                             natneg_session, self.own_server)
                    # Store info in backend so we can get it later in natneg
                    self.server_manager.add_natneg_server(
                        natneg_session, self.own_server)

                # if self.qr is not None:
                #     own_server = self.qr.get_own_server()
                #
                #     self.log(logging.DEBUG,
                #              "Adding %d to natneg server list: %s",
                #              natneg_session, own_server)
                #     self.server_manager.add_natneg_server(natneg_session,
                #                                           own_server)

            output = bytearray([0xfe, 0xfd, 0x06])
            output += utils.get_bytes_from_int(server['__session__'])
            output += bytearray(utils.get_bytes_from_int(cookie))
            output += bytearray(data)

            if self.qr is not None:
                self.log(logging.DEBUG,
                         "Forwarded data to %s:%s through QR server...",
                         forward_client[0], forward_client[1])
                self.qr.socket.sendto(output, forward_client)
            else:
                # In case we can't contact the QR server, just try sending
                # the packet directly. This isn't standard behavior but it
                # can work in some instances.
                self.log(
                    logging.DEBUG, "Forwarded data to %s:%s directly"
                    " (potential error occurred)...", forward_client[0],
                    forward_client[1])
                client_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                client_s.sendto(output, forward_client)
    def handle_packet(self, s, recv_data, addr):
        logger.log(logging.DEBUG,
                   "Connection from %s:%d..." % (addr[0], addr[1]))
        logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))
        time.sleep(0.05)

        # Make sure it's a legal packet
        if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]):
            return

        session_id = struct.unpack("<I", recv_data[8:12])[0]
        session_id_raw = recv_data[8:12]

        # Handle commands
        if recv_data[7] == '\x00':
            logger.log(
                logging.DEBUG,
                "Received initialization from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data[0:14])
            output += bytearray(
                [0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]
            )  # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00
            output[7] = 0x01  # Initialization response
            s.sendto(output, addr)

            # Try to connect to the server
            gameid = utils.get_string(recv_data, 0x15)
            client_id = "%02x" % ord(recv_data[13])

            localip_raw = recv_data[15:19]
            localip_int_le = utils.get_int(recv_data, 15)
            localip_int_be = utils.get_int_be(recv_data, 15)
            localip = '.'.join(["%d" % ord(x) for x in localip_raw])
            localport_raw = recv_data[19:21]
            localport = utils.get_short_be(localport_raw, 0)
            localaddr = (localip, localport, localip_int_le, localip_int_be)

            if session_id not in self.session_list:
                self.session_list[session_id] = {}
            if client_id not in self.session_list[session_id]:
                self.session_list[session_id][client_id] = {
                    'connected': False,
                    'addr': '',
                    'localaddr': None,
                    'serveraddr': None,
                    'gameid': None
                }

            self.session_list[session_id][client_id]['gameid'] = gameid
            self.session_list[session_id][client_id]['addr'] = addr
            self.session_list[session_id][client_id]['localaddr'] = localaddr
            clients = len(self.session_list[session_id])

            for client in self.session_list[session_id]:
                if self.session_list[session_id][client][
                        'connected'] == False:  # and self.session_list[session_id][client]['localaddr'][1] != 0:
                    if client == client_id:
                        continue

                    #if self.session_list[session_id][client]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id,
                                                      client)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(
                            gameid, session_id, client)

                    self.session_list[session_id][client][
                        'serveraddr'] = serveraddr
                    logger.log(
                        logging.DEBUG,
                        "Found server from local ip/port: %s from %d" %
                        (serveraddr, session_id))

                    publicport = self.session_list[session_id][client]['addr'][
                        1]
                    if self.session_list[session_id][client]['localaddr'][
                            1] != 0:
                        publicport = self.session_list[session_id][client][
                            'localaddr'][1]

                    if self.session_list[session_id][client][
                            'serveraddr'] != None:
                        publicport = int(self.session_list[session_id][client]
                                         ['serveraddr']['publicport'])

                    # Send to requesting client
                    output = bytearray(recv_data[0:12])
                    output += bytearray([
                        int(x) for x in self.session_list[session_id][client]
                        ['addr'][0].split('.')
                    ])
                    output += utils.get_bytes_from_short_be(publicport)

                    output += bytearray(
                        [0x42, 0x00])  # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #s.sendto(output, (self.session_list[session_id][client_id]['addr']))
                    s.sendto(
                        output,
                        (self.session_list[session_id][client_id]['addr'][0],
                         self.session_list[session_id][client_id]['addr'][1]))

                    logger.log(
                        logging.DEBUG, "Sent connection request to %s:%d..." %
                        (self.session_list[session_id][client_id]['addr'][0],
                         self.session_list[session_id][client_id]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

                    # Send to other client
                    #if self.session_list[session_id][client_id]['serveraddr'] == None:
                    serveraddr = self.get_server_info(gameid, session_id,
                                                      client_id)
                    if serveraddr == None:
                        serveraddr = self.get_server_info_alt(
                            gameid, session_id, client)

                    self.session_list[session_id][client_id][
                        'serveraddr'] = serveraddr
                    logger.log(
                        logging.DEBUG,
                        "Found server 2 from local ip/port: %s from %d" %
                        (serveraddr, session_id))

                    publicport = self.session_list[session_id][client_id][
                        'addr'][1]
                    if self.session_list[session_id][client_id]['localaddr'][
                            1] != 0:
                        publicport = self.session_list[session_id][client_id][
                            'localaddr'][1]

                    if self.session_list[session_id][client_id][
                            'serveraddr'] != None:
                        publicport = int(
                            self.session_list[session_id][client_id]
                            ['serveraddr']['publicport'])

                    output = bytearray(recv_data[0:12])
                    output += bytearray([
                        int(x) for x in self.session_list[session_id]
                        [client_id]['addr'][0].split('.')
                    ])
                    output += utils.get_bytes_from_short_be(publicport)

                    output += bytearray(
                        [0x42, 0x00])  # Unknown, always seems to be \x42\x00
                    output[7] = 0x05
                    #s.sendto(output, (self.session_list[session_id][client]['addr']))
                    s.sendto(
                        output,
                        (self.session_list[session_id][client]['addr'][0],
                         self.session_list[session_id][client]['addr'][1]))

                    logger.log(
                        logging.DEBUG, "Sent connection request to %s:%d..." %
                        (self.session_list[session_id][client]['addr'][0],
                         self.session_list[session_id][client]['addr'][1]))
                    logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x06':  # Was able to connect
            client_id = "%02x" % ord(recv_data[13])
            logger.log(
                logging.DEBUG, "Received connected command from %s:%s..." %
                (addr[0], addr[1]))

            if session_id not in self.session_list:
                return
            if client_id not in self.session_list[session_id]:
                return

            self.session_list[session_id][client_id]['connected'] = True

        elif recv_data[7] == '\x0a':  # Address check. Note: UNTESTED!
            client_id = "%02x" % ord(recv_data[13])
            logger.log(
                logging.DEBUG, "Received address check command from %s:%s..." %
                (addr[0], addr[1]))

            output = bytearray(recv_data[0:15])
            output += bytearray([int(x) for x in addr[0].split('.')])
            output += utils.get_bytes_from_short_be(addr[1])
            output += bytearray(recv_data[len(output):])

            output[7] = 0x0b
            s.sendto(output, addr)

            logger.log(
                logging.DEBUG,
                "Sent address check response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0c':  # Natify
            port_type = "%02x" % ord(recv_data[12])
            logger.log(
                logging.DEBUG,
                "Received natify command from %s:%s..." % (addr[0], addr[1]))

            output = bytearray(recv_data)
            output[7] = 0x02  # ERT Test
            s.sendto(output, addr)

            logger.log(logging.DEBUG,
                       "Sent natify response to %s:%d..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(output))

        elif recv_data[7] == '\x0d':
            client_id = "%02x" % ord(recv_data[13])
            logger.log(
                logging.DEBUG,
                "Received report command from %s:%s..." % (addr[0], addr[1]))
            logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))

            output = bytearray(recv_data)
            output[7] = 0x0e  # Report response
            s.sendto(recv_data, addr)

        else:  # Was able to connect
            logger.log(
                logging.DEBUG, "Received unknown command %02x from %s:%s..." %
                (ord(recv_data[7]), addr[0], addr[1]))