def generate_server_list_header_data(self, address, fields):
        output = bytearray()

        # Write the address
        output += bytearray([int(x) for x in address.host.split('.')])

        # Write the port
        output += utils.get_bytes_from_short(address.port, True)

        # Write number of fields that will be returned.
        key_count = len(fields)
        output += utils.get_bytes_from_short(key_count)

        if key_count != len(fields):
            # For some reason we didn't get all of the expected data.
            self.log(logging.WARNING,
                     "key_count[%d] != len(fields)[%d]",
                     key_count, len(fields))
            self.log(logging.WARNING, "%s", fields)

        # Write the fields
        for field in fields:
            output += bytearray(field) + '\0\0'

        return output
Ejemplo n.º 2
0
    def generate_server_list_data(self,
                                  address,
                                  fields,
                                  server_info,
                                  finalize=False):
        output = bytearray()
        flags_buffer = bytearray()

        if len(server_info) > 0:
            # Start server loop here instead of including all of the fields and stuff again
            flags = 0
            if len(server_info) != 0:
                flags |= ServerListFlags.HAS_KEYS_FLAG

                if "natneg" in server_info:
                    flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG

                ip = utils.get_bytes_from_int_signed(
                    int(server_info['publicip']), self.console)
                flags_buffer += ip

                flags |= ServerListFlags.NONSTANDARD_PORT_FLAG

                if server_info['publicport'] != "0":
                    flags_buffer += utils.get_bytes_from_short(
                        int(server_info['publicport']), True)
                else:
                    flags_buffer += utils.get_bytes_from_short(
                        int(server_info['localport']), True)

                if "localip0" in server_info:
                    # How to handle multiple localips?
                    flags |= ServerListFlags.PRIVATE_IP_FLAG
                    flags_buffer += bytearray([
                        int(x) for x in server_info['localip0'].split('.')
                    ])  #ip

                if "localport" in server_info:
                    flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG
                    flags_buffer += utils.get_bytes_from_short(
                        int(server_info['localport']), True)

                flags |= ServerListFlags.ICMP_IP_FLAG
                flags_buffer += bytearray(
                    [int(x) for x in "0.0.0.0".split('.')])

                output += bytearray([flags & 0xff])
                output += flags_buffer

                if (flags & ServerListFlags.HAS_KEYS_FLAG):
                    # Write data for associated fields
                    if 'requested' in server_info:
                        for field in fields:
                            output += '\xff' + bytearray(
                                server_info['requested'][field]) + '\0'

        return output
    def generate_server_list_data(self, address, fields, server_info, finalize = False):
        output = bytearray()
        flags_buffer = bytearray()

        if len(server_info) > 0:
            # Start server loop here instead of including all of the fields and stuff again
            flags = 0
            if len(server_info) != 0:
                flags |= ServerListFlags.HAS_KEYS_FLAG

                if "natneg" in server_info:
                    flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG

                ip = utils.get_bytes_from_int_signed(int(server_info['publicip']), self.console)
                flags_buffer += ip

                flags |= ServerListFlags.NONSTANDARD_PORT_FLAG

                if server_info['publicport'] != "0":
                    flags_buffer += utils.get_bytes_from_short(int(server_info['publicport']), True)
                else:
                    flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True)

                if "localip0" in server_info:
                    # How to handle multiple localips?
                    flags |= ServerListFlags.PRIVATE_IP_FLAG
                    flags_buffer += bytearray([int(x) for x in server_info['localip0'].split('.')]) #ip

                if "localport" in server_info:
                    flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG
                    flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True)

                flags |= ServerListFlags.ICMP_IP_FLAG
                flags_buffer += bytearray([int(x) for x in "0.0.0.0".split('.')])

                output += bytearray([flags & 0xff])
                output += flags_buffer

                if (flags & ServerListFlags.HAS_KEYS_FLAG):
                    # Write data for associated fields
                    if 'requested' in server_info:
                        for field in fields:
                            output += '\xff' + bytearray(server_info['requested'][field]) + '\0'

        return output
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))
Ejemplo n.º 5
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))
Ejemplo n.º 6
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 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])
Ejemplo n.º 8
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))
Ejemplo n.º 9
0
    def generate_server_list_data(self, address, fields, server_info):
        output = bytearray()

        # Write the address
        output += bytearray([int(x) for x in address.host.split('.')])

        # Write the port
        output += utils.get_bytes_from_short_be(address.port)

        #if len(server_info) > 0:
        if True:
            # Write number of fields that will be returned.
            key_count = len(fields)
            output += utils.get_bytes_from_short(key_count)

            if key_count != len(fields):
                # For some reason we didn't get all of the expected data.
                self.log(logging.WARNING, "key_count[%d] != len(fields)[%d]" % (key_count, len(fields)))
                self.log(logging.WARNING, fields)

            flags_buffer = bytearray()

            # Write the fields
            for field in fields:
                output += bytearray(field) + '\0\0'

            # Start server loop here instead of including all of the fields and stuff again
            flags = 0
            if len(server_info) != 0:
                flags |= ServerListFlags.HAS_KEYS_FLAG

                if "natneg" in server_info:
                    flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG

                ip = 0
                if self.console != 0:
                    ip = utils.get_bytes_from_int_be(int(server_info['publicip'])) # Wii
                    flags_buffer += ip
                else:
                    ip = utils.get_bytes_from_int(int(server_info['publicip'])) # DS
                    flags_buffer += ip

                flags |= ServerListFlags.NONSTANDARD_PORT_FLAG
                flags_buffer += utils.get_bytes_from_short_be(int(server_info['publicport']))

                if "localip0" in server_info:
                    flags |= ServerListFlags.PRIVATE_IP_FLAG
                    flags_buffer += ip #bytearray([int(x) for x in server_info['localip'].split('.')])

                if "localport" in server_info:
                    flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG
                    flags_buffer += utils.get_bytes_from_short_be(int(server_info['localport']))

                flags |= ServerListFlags.ICMP_IP_FLAG
                flags_buffer += bytearray([int(x) for x in "0.0.0.0".split('.')])

                output += bytearray([flags & 0xff])
                output += flags_buffer

                if (flags & ServerListFlags.HAS_KEYS_FLAG):
                    # Write data for associated fields
                    for field in fields:
                        output += '\xff' + bytearray(server_info['requested'][field]) + '\0'

            output += '\0'
            output += utils.get_bytes_from_int(-1)

        return output
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))
Ejemplo n.º 11
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):
        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())