123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818 |
- """DWC Network Server Emulator
- Copyright (C) 2014 polaris-
- Copyright (C) 2014 ToadKing
- Copyright (C) 2016 Sepalani
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- Server emulator for *.available.gs.nintendowifi.net
- and *.master.gs.nintendowifi.net
- Query and Reporting:
- http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview
- http://wiki.tockdom.com/wiki/Server_NATNEG
- """
- import logging
- import socketserver
- import threading
- import time
- import queue
- import gamespy.gs_utility as gs_utils
- import other.utils as utils
- import traceback
- from multiprocessing.managers import BaseManager
- import dwc_config
- logger = dwc_config.get_logger('GameSpyNatNegServer')
- class GameSpyServerDatabase(BaseManager):
- pass
- GameSpyServerDatabase.register("get_server_list")
- GameSpyServerDatabase.register("modify_server_list")
- GameSpyServerDatabase.register("find_servers")
- GameSpyServerDatabase.register("find_server_by_address")
- GameSpyServerDatabase.register("find_server_by_local_address")
- GameSpyServerDatabase.register("add_natneg_server")
- GameSpyServerDatabase.register("get_natneg_server")
- GameSpyServerDatabase.register("delete_natneg_server")
- 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_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_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_ertack(nn, recv_data, addr, socket):
- """Command: 0x03 - NN_ERTACK.
- Reply by the client for record NN_ERTTEST (0x02).
- Only the record type is changed.
- Example:
- fd fc 1e 66 6a b2 03 03 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
- 03 - 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.INFO, "Received ERT acknowledgement from %s:%d", *addr)
- 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_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_ack(nn, recv_data, addr, socket):
- """Command: 0x06 - NN_CONNECT_ACK.
- Reply by the client for record NN_CONNECT (0x05).
- Example:
- fd fc 1e 66 6a b2 03 06 3d f1 00 71 90 00 cd a0
- 80 00 00 00 90
- Description:
- fd fc 1e 66 6a b2 - NATNEG magic
- 03 - NATNEG version
- 06 - NATNEG record type
- 3d f1 00 71 - Session id
- 90 - Port type (0x00, 0x80 or 0x90)
- 00 - Client index (0x00 - Client,
- 0x01 - Host)
- cd - Use game port?
- a0 80 00 00 - Local IP?
- 00 90 - Local port?
- """
- client_id = "%02x" % ord(recv_data[13])
- session_id = utils.get_int(recv_data, 8)
- logger.log(logging.DEBUG,
- "Received connected command from %s:%d...",
- *addr)
- if session_id in nn.session_list and \
- client_id in nn.session_list[session_id]:
- nn.session_list[session_id][client_id]['connected'] = True
- 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))
- 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))
- 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_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_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))
- 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_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))
- 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))
- 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))
- class GameSpyNatNegUDPServerHandler(socketserver.BaseRequestHandler):
- """GameSpy NAT Negotiation handler."""
- nn_magics = bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2])
- nn_commands = {
- '\x00': handle_natneg_init,
- '\x01': handle_natneg_initack,
- '\x02': handle_natneg_erttest,
- '\x03': handle_natneg_ertack,
- '\x04': handle_natneg_stateupdate,
- '\x05': handle_natneg_connect,
- '\x06': handle_natneg_connect_ack,
- '\x07': handle_natneg_connect_ping,
- '\x08': handle_natneg_backup_test,
- '\x09': handle_natneg_backup_ack,
- '\x0A': handle_natneg_address_check,
- '\x0B': handle_natneg_address_reply,
- '\x0C': handle_natneg_natify_request,
- '\x0D': handle_natneg_report,
- '\x0E': handle_natneg_report_ack,
- '\x0F': handle_natneg_preinit,
- '\x10': handle_natneg_preinit_ack
- }
- 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())
- class GameSpyNatNegUDPServer(socketserver.UDPServer):
- """GameSpy NAT Negotiation server."""
- def __init__(self,
- server_address=dwc_config.get_ip_port('GameSpyNatNegServer'),
- RequestHandlerClass=GameSpyNatNegUDPServerHandler,
- bind_and_activate=True):
- socketserver.UDPServer.__init__(self,
- server_address,
- RequestHandlerClass,
- bind_and_activate)
- self.session_list = {}
- self.natneg_preinit_session = {}
- self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg")
- self.server_manager = GameSpyServerDatabase(
- address=dwc_config.get_ip_port('GameSpyManager'),
- authkey=b""
- )
- self.server_manager.connect()
- self.write_queue = queue.Queue()
- threading.Thread(target=self.write_queue_worker).start()
- def write_queue_send(self, data, address, socket):
- time.sleep(0.05)
- socket.sendto(data, address)
- def write_queue_worker(self):
- while True:
- data, address, socket = self.write_queue.get()
- threading.Thread(target=self.write_queue_send,
- args=(data, address, socket)).start()
- self.write_queue.task_done()
- def get_server_info(self, gameid, session_id, client_id):
- """Get server by public IP."""
- server = None
- ip_str = self.session_list[session_id][client_id]['addr'][0]
- servers = self.server_manager.get_natneg_server(session_id) \
- ._getvalue()
-
- if servers is None:
- return None
-
- for console in [False, True]:
- if server is not None:
- break
- ip = str(utils.get_ip_from_str(ip_str, console))
- server = next((s for s in servers if s['publicip'] == ip), None)
- return server
- def get_server_info_alt(self, gameid, session_id, client_id):
- """Get server by local address."""
- server = None
- ip_str = self.session_list[session_id][client_id]['addr'][0]
- for console in [False, True]:
- if server is not None:
- break
- ip = str(utils.get_ip_from_str(ip_str, console))
- server = self.server_manager.find_server_by_local_address(
- ip,
- self.session_list[session_id][client_id]['localaddr'],
- self.session_list[session_id][client_id]['gameid']
- )._getvalue()
- return server
- def get_server_addr(self, gameid, session_id, client_id):
- """Get server address."""
- return \
- self.get_server_info(gameid, session_id, client_id) or \
- self.get_server_info_alt(gameid, session_id, client_id)
- class GameSpyNatNegServer(object):
- def start(self):
- server = GameSpyNatNegUDPServer()
- logger.log(logging.INFO, "Server is now listening on %s:%d...",
- *server.server_address)
- server.serve_forever()
- if __name__ == "__main__":
- natneg_server = GameSpyNatNegServer()
- natneg_server.start()
|