gamespy_natneg_server.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. """DWC Network Server Emulator
  2. Copyright (C) 2014 polaris-
  3. Copyright (C) 2014 ToadKing
  4. Copyright (C) 2016 Sepalani
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU Affero General Public License as
  7. published by the Free Software Foundation, either version 3 of the
  8. License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU Affero General Public License for more details.
  13. You should have received a copy of the GNU Affero General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. Server emulator for *.available.gs.nintendowifi.net
  16. and *.master.gs.nintendowifi.net
  17. Query and Reporting:
  18. http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview
  19. http://wiki.tockdom.com/wiki/Server_NATNEG
  20. """
  21. import logging
  22. import socketserver
  23. import threading
  24. import time
  25. import queue
  26. import gamespy.gs_utility as gs_utils
  27. import other.utils as utils
  28. import traceback
  29. from multiprocessing.managers import BaseManager
  30. import dwc_config
  31. logger = dwc_config.get_logger('GameSpyNatNegServer')
  32. class GameSpyServerDatabase(BaseManager):
  33. pass
  34. GameSpyServerDatabase.register("get_server_list")
  35. GameSpyServerDatabase.register("modify_server_list")
  36. GameSpyServerDatabase.register("find_servers")
  37. GameSpyServerDatabase.register("find_server_by_address")
  38. GameSpyServerDatabase.register("find_server_by_local_address")
  39. GameSpyServerDatabase.register("add_natneg_server")
  40. GameSpyServerDatabase.register("get_natneg_server")
  41. GameSpyServerDatabase.register("delete_natneg_server")
  42. def handle_natneg(nn, recv_data, addr, socket):
  43. """Command: Unknown."""
  44. logger.log(logging.DEBUG,
  45. "Received unknown command %02x from %s:%d...",
  46. ord(recv_data[7]), *addr)
  47. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  48. def handle_natneg_init(nn, recv_data, addr, socket):
  49. """Command: 0x00 - NN_INIT.
  50. Send by the client to initialize the connection.
  51. Example:
  52. fd fc 1e 66 6a b2 03 00 3d f1 00 71 00 00 01 0a
  53. 00 01 e2 00 00 6d 61 72 69 6f 6b 61 72 74 77 69
  54. 69 00
  55. Description:
  56. fd fc 1e 66 6a b2 - NATNEG magic
  57. 03 - NATNEG version
  58. 00 - NATNEG record type
  59. 3d f1 00 71 - Session id
  60. 00 - Port type (between 0x00 and 0x03)
  61. 00 - Client index (0x00 - Client,
  62. 0x01 - Host)
  63. 01 - Use game port
  64. 0a 00 01 e2 - Local IP
  65. 00 00 - Local port
  66. GAME_NAME 00 - Game name
  67. """
  68. logger.log(logging.DEBUG, "Received initialization from %s:%d...", *addr)
  69. session_id = utils.get_int(recv_data, 8)
  70. output = bytearray(recv_data[0:14])
  71. # Checked with Tetris DS, Mario Kart DS, and Metroid Prime
  72. # Hunters, and this seems to be the standard response to 0x00
  73. output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea])
  74. output[7] = 0x01 # Initialization response
  75. nn.write_queue.put((output, addr, socket))
  76. # Try to connect to the server
  77. gameid = utils.get_string(recv_data, 0x15)
  78. client_id = "%02x" % ord(recv_data[13])
  79. localaddr = utils.get_local_addr(recv_data, 15)
  80. nn.session_list \
  81. .setdefault(session_id, {}) \
  82. .setdefault(client_id,
  83. {
  84. 'connected': False,
  85. 'addr': '',
  86. 'localaddr': None,
  87. 'serveraddr': None,
  88. 'gameid': None
  89. })
  90. # In fact, it's a pointer
  91. client_id_session = nn.session_list[session_id][client_id]
  92. client_id_session['gameid'] = gameid
  93. client_id_session['addr'] = addr
  94. client_id_session['localaddr'] = localaddr
  95. for client in nn.session_list[session_id]:
  96. # Another pointer
  97. client_session = nn.session_list[session_id][client]
  98. if client_session['connected'] or client == client_id:
  99. continue
  100. # --- Send to requesting client
  101. # Get server info
  102. serveraddr = nn.get_server_addr(gameid, session_id, client)
  103. client_session['serveraddr'] = serveraddr
  104. logger.log(logging.DEBUG,
  105. "Found server from local ip/port: %s from %d",
  106. serveraddr, session_id)
  107. # Get public port
  108. if client_session['serveraddr'] is not None:
  109. publicport = int(client_session['serveraddr']['publicport'])
  110. else:
  111. publicport = \
  112. client_session['localaddr'][1] or \
  113. client_session['addr'][1]
  114. output = bytearray(recv_data[0:12])
  115. output += utils.get_bytes_from_ip_str(client_session['addr'][0])
  116. output += utils.get_bytes_from_short(publicport, True)
  117. # Unknown, always seems to be \x42\x00
  118. output += bytearray([0x42, 0x00])
  119. output[7] = 0x05 # NN_CONNECT
  120. nn.write_queue.put((output, client_id_session['addr'], socket))
  121. logger.log(logging.DEBUG,
  122. "Sent connection request to %s:%d...",
  123. *client_id_session['addr'])
  124. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  125. # --- Send to other client
  126. # Get server info
  127. serveraddr = nn.get_server_addr(gameid, session_id, client_id)
  128. client_id_session['serveraddr'] = serveraddr
  129. logger.log(logging.DEBUG,
  130. "Found server 2 from local ip/port: %s from %d",
  131. serveraddr, session_id)
  132. # Get public port
  133. if client_id_session['serveraddr'] is not None:
  134. publicport = int(client_id_session['serveraddr']['publicport'])
  135. else:
  136. publicport = \
  137. client_id_session['localaddr'][1] or \
  138. client_id_session['addr'][1]
  139. output = bytearray(recv_data[0:12])
  140. output += utils.get_bytes_from_ip_str(client_id_session['addr'][0])
  141. output += utils.get_bytes_from_short(publicport, True)
  142. # Unknown, always seems to be \x42\x00
  143. output += bytearray([0x42, 0x00])
  144. output[7] = 0x05 # NN_CONNECT
  145. nn.write_queue.put((output, client_session['addr'], socket))
  146. logger.log(logging.DEBUG,
  147. "Sent connection request to %s:%d...",
  148. *client_session['addr'])
  149. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  150. def handle_natneg_initack(nn, recv_data, addr, socket):
  151. """Command: 0x01 - NN_INITACK.
  152. Reply by the server for record NN_INIT (0x00).
  153. Example:
  154. fd fc 1e 66 6a b2 03 01 3d f1 00 71 00 00 ff ff
  155. 6d 16 b5 7d ea
  156. Description:
  157. fd fc 1e 66 6a b2 - NATNEG magic
  158. 03 - NATNEG version
  159. 01 - NATNEG record type
  160. 3d f1 00 71 - Session id
  161. 00 - Port type (between 0x00 and 0x03)
  162. 00 - Client index (0x00 - Client,
  163. 0x01 - Host)
  164. ff - Use game port (-1)? Dummy value?
  165. ff 6d 16 b5 - Local IP? Dummy value?
  166. 7d ea - Local port? Hex speak of "Idea"? Dummy value?
  167. """
  168. logger.log(logging.WARNING,
  169. "Received server record type command NN_INITACK (0x01)"
  170. " from %s:%d...", *addr)
  171. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  172. def handle_natneg_erttest(nn, recv_data, addr, socket):
  173. """Command: 0x02 - NN_ERTTEST.
  174. Reply by the server for record NN_NATIFY_REQUEST (0x0C).
  175. Example:
  176. fd fc 1e 66 6a b2 03 02 00 00 03 09 02 00 00 00
  177. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  178. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  179. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  180. 00 00 00 00 00 00 00 00 00
  181. Description:
  182. fd fc 1e 66 6a b2 - NATNEG magic
  183. 03 - NATNEG version
  184. 02 - NATNEG record type
  185. 00 00 03 09 - Session id
  186. 02 - Port type (between 0x00 and 0x03)
  187. - 60 bytes padding?
  188. 00 - Client index (0x00 - Client,
  189. 0x01 - Host)
  190. 00 - NATNEG result?
  191. 00 00 00 00 - NAT type?
  192. 00 00 00 00 - NAT mapping scheme?
  193. 00 (x50) - Game name?
  194. """
  195. logger.log(logging.WARNING,
  196. "Received server record type command NN_ERTTEST (0x02)"
  197. " from %s:%d...", *addr)
  198. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  199. def handle_natneg_ertack(nn, recv_data, addr, socket):
  200. """Command: 0x03 - NN_ERTACK.
  201. Reply by the client for record NN_ERTTEST (0x02).
  202. Only the record type is changed.
  203. Example:
  204. fd fc 1e 66 6a b2 03 03 00 00 03 09 02 00 00 00
  205. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  206. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  207. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  208. 00 00 00 00 00 00 00 00 00
  209. Description:
  210. fd fc 1e 66 6a b2 - NATNEG magic
  211. 03 - NATNEG version
  212. 03 - NATNEG record type
  213. 00 00 03 09 - Session id
  214. 02 - Port type (between 0x00 and 0x03)
  215. - 60 bytes padding?
  216. 00 - Client index (0x00 - Client,
  217. 0x01 - Host)
  218. 00 - NATNEG result?
  219. 00 00 00 00 - NAT type?
  220. 00 00 00 00 - NAT mapping scheme?
  221. 00 (x50) - Game name?
  222. """
  223. logger.log(logging.INFO, "Received ERT acknowledgement from %s:%d", *addr)
  224. def handle_natneg_stateupdate(nn, recv_data, addr, socket):
  225. """Command: 0x04 - NN_STATEUPDATE.
  226. TODO
  227. Example:
  228. TODO
  229. Description:
  230. TODO
  231. """
  232. logger.log(logging.WARNING,
  233. "Received unimplemented command NN_STATEUPDATE (0x04)"
  234. " from %s:%d...", *addr)
  235. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  236. def handle_natneg_connect(nn, recv_data, addr, socket):
  237. """Command: 0x05 - NN_CONNECT.
  238. Reply by the server for record NN_INIT (0x00).
  239. Example:
  240. fd fc 1e 66 6a b2 03 05 3d f1 00 71 18 ab ed 7a
  241. da 00 42 00
  242. Description:
  243. fd fc 1e 66 6a b2 - NATNEG magic
  244. 03 - NATNEG version
  245. 05 - NATNEG record type
  246. 3d f1 00 71 - Session id
  247. 18 ab ed 7a - Remote IP
  248. da 00 - Remote port
  249. 42 - Got remote data
  250. 00 - Finished
  251. """
  252. logger.log(logging.WARNING,
  253. "Received server record type command NN_CONNECT (0x05)"
  254. " from %s:%d...", *addr)
  255. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  256. def handle_natneg_connect_ack(nn, recv_data, addr, socket):
  257. """Command: 0x06 - NN_CONNECT_ACK.
  258. Reply by the client for record NN_CONNECT (0x05).
  259. Example:
  260. fd fc 1e 66 6a b2 03 06 3d f1 00 71 90 00 cd a0
  261. 80 00 00 00 90
  262. Description:
  263. fd fc 1e 66 6a b2 - NATNEG magic
  264. 03 - NATNEG version
  265. 06 - NATNEG record type
  266. 3d f1 00 71 - Session id
  267. 90 - Port type (0x00, 0x80 or 0x90)
  268. 00 - Client index (0x00 - Client,
  269. 0x01 - Host)
  270. cd - Use game port?
  271. a0 80 00 00 - Local IP?
  272. 00 90 - Local port?
  273. """
  274. client_id = "%02x" % ord(recv_data[13])
  275. session_id = utils.get_int(recv_data, 8)
  276. logger.log(logging.DEBUG,
  277. "Received connected command from %s:%d...",
  278. *addr)
  279. if session_id in nn.session_list and \
  280. client_id in nn.session_list[session_id]:
  281. nn.session_list[session_id][client_id]['connected'] = True
  282. def handle_natneg_connect_ping(nn, recv_data, addr, socket):
  283. """Command: 0x07 - NN_CONNECT_PING.
  284. Looks like NN_CONNECT but between clients.
  285. The server shouldn't be involved.
  286. Example:
  287. fd fc 1e 66 6a b2 03 07 ?? ?? ?? ?? ?? ?? ?? ??
  288. ?? ?? ?? ??
  289. Description:
  290. fd fc 1e 66 6a b2 - NATNEG magic
  291. 03 - NATNEG version
  292. 07 - NATNEG record type
  293. ?? ?? ?? ?? - Session id
  294. ?? ?? ?? ?? - Remote IP
  295. ?? ?? - Remote port
  296. ?? - Sequence counter (0 or 1)
  297. ?? - Error
  298. """
  299. logger.log(logging.WARNING,
  300. "Received unimplemented command NN_CONNECT_PING (0x07)"
  301. " from %s:%d...", *addr)
  302. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  303. def handle_natneg_backup_test(nn, recv_data, addr, socket):
  304. """Command: 0x08 - NN_BACKUP_TEST.
  305. Send by the client.
  306. Example:
  307. TODO
  308. Description:
  309. Untested
  310. """
  311. logger.log(logging.DEBUG, "Received backup command from %s:%d...", *addr)
  312. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  313. # Backup response
  314. output = bytearray(recv_data)
  315. output[7] = 0x09 # NN_BACKUP_ACK
  316. nn.write_queue.put((output, addr, socket))
  317. def handle_natneg_backup_ack(nn, recv_data, addr, socket):
  318. """Command: 0x09 - NN_BACKUP_ACK.
  319. Reply by the server for record NN_BACKUP_TEST (0x08).
  320. Only the record type is changed.
  321. Example:
  322. TODO
  323. Description:
  324. TODO
  325. """
  326. logger.log(logging.WARNING,
  327. "Received server record type command NN_BACKUP_ACK (0x09)"
  328. " from %s:%d...", *addr)
  329. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  330. def handle_natneg_address_check(nn, recv_data, addr, socket):
  331. """Command: 0x0A - NN_ADDRESS_CHECK.
  332. Send by the client during connection test.
  333. Example:
  334. fd fc 1e 66 6a b2 03 0a 00 00 00 00 01 00 00 00
  335. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  336. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  337. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  338. 00 00 00 00 00 00 00 00 00
  339. Description:
  340. fd fc 1e 66 6a b2 - NATNEG magic
  341. 03 - NATNEG version
  342. 0a - NATNEG record type
  343. 00 00 00 00 - Session id
  344. 01 - Port type (between 0x00 and 0x03)
  345. - 60 bytes padding?
  346. 00 - Client index (0x00 - Client,
  347. 0x01 - Host)
  348. 00 - NATNEG result?
  349. 00 00 00 00 - NAT type?
  350. 00 00 00 00 - NAT mapping scheme?
  351. 00 (x50) - Game name?
  352. """
  353. client_id = "%02x" % ord(recv_data[13])
  354. logger.log(logging.DEBUG,
  355. "Received address check command from %s:%d...",
  356. *addr)
  357. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  358. output = bytearray(recv_data[0:15])
  359. output += utils.get_bytes_from_ip_str(addr[0])
  360. output += utils.get_bytes_from_short(addr[1], True)
  361. output += bytearray(recv_data[len(output):])
  362. output[7] = 0x0b # NN_ADDRESS_REPLY
  363. nn.write_queue.put((output, addr, socket))
  364. logger.log(logging.DEBUG, "Sent address check response to %s:%d...", *addr)
  365. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  366. def handle_natneg_address_reply(nn, recv_data, addr, socket):
  367. """Command: 0x0B - NN_ADDRESS_REPLY.
  368. Reply by the server for record NN_ADDRESS_CHECK (0x0A).
  369. Example:
  370. fd fc 1e 66 6a b2 03 0b 00 00 00 03 01 00 00 25
  371. c9 e2 8a 91 e4
  372. Description:
  373. fd fc 1e 66 6a b2 - NATNEG magic
  374. 03 - NATNEG version
  375. 0b - NATNEG record type
  376. 00 00 00 03 - Session id
  377. 01 - Port type (between 0x00 and 0x03)
  378. 00 - Client index (0x00 - Client,
  379. 0x01 - Host)
  380. 00 - Use game port
  381. 25 c9 e2 8a - Public IP
  382. 91 e4 - Public port
  383. """
  384. logger.log(logging.WARNING,
  385. "Received server record type command NN_ADDRESS_REPLY (0x0B)"
  386. " from %s:%d...", *addr)
  387. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  388. def handle_natneg_natify_request(nn, recv_data, addr, socket):
  389. """Command: 0x0C - NN_NATIFY_REQUEST.
  390. Send by the client during connection test.
  391. Example:
  392. fd fc 1e 66 6a b2 03 0c 00 00 03 09 01 00 00 00
  393. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  394. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  395. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  396. 00 00 00 00 00 00 00 00 00
  397. Description:
  398. fd fc 1e 66 6a b2 - NATNEG magic
  399. 03 - NATNEG version
  400. 0c - NATNEG record type
  401. 00 00 03 09 - Session id
  402. 01 - Port type (between 0x00 and 0x03)
  403. - 60 bytes padding?
  404. 00 - Client index (0x00 - Client,
  405. 0x01 - Host)
  406. 00 - NATNEG result?
  407. 00 00 00 00 - NAT type?
  408. 00 00 00 00 - NAT mapping scheme?
  409. 00 (x50) - Game name?
  410. """
  411. port_type = "%02x" % ord(recv_data[12])
  412. logger.log(logging.DEBUG, "Received natify command from %s:%d...", *addr)
  413. output = bytearray(recv_data)
  414. output[7] = 0x02 # ERT Test
  415. nn.write_queue.put((output, addr, socket))
  416. logger.log(logging.DEBUG, "Sent natify response to %s:%d...", *addr)
  417. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
  418. def handle_natneg_report(nn, recv_data, addr, socket):
  419. """Command: 0x0D - NN_REPORT.
  420. Send by the client.
  421. Example:
  422. fd fc 1e 66 6a b2 03 0d 3d f1 00 71 00 00 01 00
  423. 00 00 06 00 00 00 00 6d 61 72 69 6f 6b 61 72 74
  424. 77 69 69 00 00 00 00 00 00 00 00 00 00 00 00 00
  425. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  426. 00 00 00 00 00 00 00 00 00
  427. Description:
  428. fd fc 1e 66 6a b2 - NATNEG magic
  429. 03 - NATNEG version
  430. 0d - NATNEG record type
  431. 3d f1 00 71 - Session id
  432. 00 - Port type (0x00, 0x80 or 0x90)
  433. 00 - Client index (0x00 - Client,
  434. 0x01 - Host)
  435. 01 - NATNEG result (0x00 - Error,
  436. 0x01 - Success)
  437. 00 00 00 06 - NAT type (0x00 - No NAT,
  438. 0x01 - Firewall only,
  439. 0x02 - Full cone,
  440. 0x03 - Restricted cone,
  441. 0x04 - Port restricted cone,
  442. 0x05 - Symmetric,
  443. 0x06 - Unknown)
  444. 00 00 00 00 - NAT mapping scheme (0x00 - Unknown,
  445. 0x01 - Private same as public,
  446. 0x02 - Consistent port,
  447. 0x03 - Incremental,
  448. 0x04 - Mixed)
  449. GAME_NAME 00 - Game name (GAME_NAME is 49 bytes length)
  450. """
  451. logger.log(logging.DEBUG, "Received report command from %s:%d...", *addr)
  452. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  453. # Report response
  454. output = bytearray(recv_data[:21])
  455. output[7] = 0x0e # Report response
  456. output[14] = 0 # Clear byte to match real server's response
  457. nn.write_queue.put((output, addr, socket))
  458. def handle_natneg_report_ack(nn, recv_data, addr, socket):
  459. """Command: 0x0E - NN_REPORT_ACK.
  460. Reply by the server for record NN_REPORT (0x0D).
  461. Example:
  462. fd fc 1e 66 6a b2 03 0e 3d f1 00 71 00 00 00 00
  463. 00 00 06 00 00
  464. Description:
  465. fd fc 1e 66 6a b2 - NATNEG magic
  466. 03 - NATNEG version
  467. 0e - NATNEG record type
  468. 3d f1 00 71 - Session id
  469. 00 - Port type (0x00, 0x80 or 0x90)
  470. 00 - Client index (0x00 - Client,
  471. 0x01 - Host)
  472. 00 - NATNEG result (0x00 - Error,
  473. 0x01 - Success)
  474. 00 00 00 06 - NAT type (0x00 - No NAT,
  475. 0x01 - Firewall only,
  476. 0x02 - Full cone,
  477. 0x03 - Restricted cone,
  478. 0x04 - Port restricted cone,
  479. 0x05 - Symmetric,
  480. 0x06 - Unknown)
  481. """
  482. logger.log(logging.WARNING,
  483. "Received server record type command NN_REPORT_ACK (0x0E)"
  484. " from %s:%d...", *addr)
  485. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  486. def handle_natneg_preinit(nn, recv_data, addr, socket):
  487. """Command: 0x0F - NN_PREINIT.
  488. Natneg v4 command thanks to Pipian.
  489. Only seems to be used in very few DS games, namely,
  490. Pokemon Black/White/Black 2/White 2.
  491. Example:
  492. fd fc 1e 66 6a b2 04 0f b5 e0 95 2a 00 24 38 b2
  493. b3 5e
  494. Description:
  495. fd fc 1e 66 6a b2 - NATNEG magic
  496. 04 - NATNEG version
  497. 0f - NATNEG record type
  498. b5 e0 95 2a - Session id
  499. 00 - Client index (0x00 - Client,
  500. 0x01 - Host)
  501. 24 - State (0x00 - Waiting for client,
  502. 0x01 - Waiting for matchup,
  503. 0x02 - Ready)
  504. 38 b2 b3 5e - Other client's session id
  505. """
  506. logger.log(logging.DEBUG, "Received pre-init command from %s:%d...", *addr)
  507. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  508. session = utils.get_int(recv_data[-4:], 0)
  509. # Report response
  510. output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0])
  511. output[7] = 0x10 # Pre-init response
  512. if not session:
  513. # What's the correct behavior when session == 0?
  514. output[13] = 2
  515. elif session in nn.natneg_preinit_session:
  516. # Should this be sent to both clients or just the one that
  517. # connected most recently?
  518. # I can't tell from a one sided packet capture of Pokemon.
  519. # For the time being, send to both clients just in case.
  520. output[13] = 2
  521. nn.write_queue.put((output, nn.natneg_preinit_session[session],
  522. socket))
  523. output[12] = (1, 0)[output[12]] # Swap the index
  524. del nn.natneg_preinit_session[session]
  525. else:
  526. output[13] = 0
  527. nn.natneg_preinit_session[session] = addr
  528. nn.write_queue.put((output, addr, socket))
  529. def handle_natneg_preinit_ack(nn, recv_data, addr, socket):
  530. """Command: 0x10 - NN_PREINIT_ACK.
  531. Reply by the server for record NN_PREINIT (0x0F).
  532. Example:
  533. fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 00 00 00 00
  534. 00 00
  535. After receiving other client's PREINIT:
  536. fd fc 1e 66 6a b2 04 10 b5 e0 95 2a 01 02 00 00
  537. 00 00
  538. Description:
  539. fd fc 1e 66 6a b2 - NATNEG magic
  540. 04 - NATNEG version
  541. 10 - NATNEG record type
  542. b5 e0 95 2a - Session id
  543. 00 - Client index (0x00 - Client,
  544. 0x01 - Host)
  545. 00 - State (0x00 - Waiting for client,
  546. 0x01 - Waiting for matchup,
  547. 0x02 - Ready)
  548. 00 00 00 00 - Other client's session id (or empty)
  549. """
  550. logger.log(logging.WARNING,
  551. "Received server record type command NN_PREINIT_ACK (0x10)"
  552. " from %s:%d...", *addr)
  553. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  554. class GameSpyNatNegUDPServerHandler(socketserver.BaseRequestHandler):
  555. """GameSpy NAT Negotiation handler."""
  556. nn_magics = bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2])
  557. nn_commands = {
  558. '\x00': handle_natneg_init,
  559. '\x01': handle_natneg_initack,
  560. '\x02': handle_natneg_erttest,
  561. '\x03': handle_natneg_ertack,
  562. '\x04': handle_natneg_stateupdate,
  563. '\x05': handle_natneg_connect,
  564. '\x06': handle_natneg_connect_ack,
  565. '\x07': handle_natneg_connect_ping,
  566. '\x08': handle_natneg_backup_test,
  567. '\x09': handle_natneg_backup_ack,
  568. '\x0A': handle_natneg_address_check,
  569. '\x0B': handle_natneg_address_reply,
  570. '\x0C': handle_natneg_natify_request,
  571. '\x0D': handle_natneg_report,
  572. '\x0E': handle_natneg_report_ack,
  573. '\x0F': handle_natneg_preinit,
  574. '\x10': handle_natneg_preinit_ack
  575. }
  576. def handle(self):
  577. """Handle NAT Negotiation request."""
  578. recv_data, socket = self.request
  579. addr = self.client_address
  580. logger.log(logging.DEBUG, "Connection from %s:%d...", *addr)
  581. logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data))
  582. # Make sure it's a legal packet
  583. if not recv_data.startswith(self.nn_magics):
  584. logger.log(logging.ERROR, "Aborted due to illegal packet!")
  585. return
  586. # Handle commands
  587. try:
  588. command = self.nn_commands.get(recv_data[7], handle_natneg)
  589. command(self.server, recv_data, addr, socket)
  590. except:
  591. logger.log(logging.ERROR, "Failed to handle command!")
  592. logger.log(logging.ERROR, "%s", traceback.format_exc())
  593. class GameSpyNatNegUDPServer(socketserver.UDPServer):
  594. """GameSpy NAT Negotiation server."""
  595. def __init__(self,
  596. server_address=dwc_config.get_ip_port('GameSpyNatNegServer'),
  597. RequestHandlerClass=GameSpyNatNegUDPServerHandler,
  598. bind_and_activate=True):
  599. socketserver.UDPServer.__init__(self,
  600. server_address,
  601. RequestHandlerClass,
  602. bind_and_activate)
  603. self.session_list = {}
  604. self.natneg_preinit_session = {}
  605. self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg")
  606. self.server_manager = GameSpyServerDatabase(
  607. address=dwc_config.get_ip_port('GameSpyManager'),
  608. authkey=b""
  609. )
  610. self.server_manager.connect()
  611. self.write_queue = queue.Queue()
  612. threading.Thread(target=self.write_queue_worker).start()
  613. def write_queue_send(self, data, address, socket):
  614. time.sleep(0.05)
  615. socket.sendto(data, address)
  616. def write_queue_worker(self):
  617. while True:
  618. data, address, socket = self.write_queue.get()
  619. threading.Thread(target=self.write_queue_send,
  620. args=(data, address, socket)).start()
  621. self.write_queue.task_done()
  622. def get_server_info(self, gameid, session_id, client_id):
  623. """Get server by public IP."""
  624. server = None
  625. ip_str = self.session_list[session_id][client_id]['addr'][0]
  626. servers = self.server_manager.get_natneg_server(session_id) \
  627. ._getvalue()
  628. if servers is None:
  629. return None
  630. for console in [False, True]:
  631. if server is not None:
  632. break
  633. ip = str(utils.get_ip_from_str(ip_str, console))
  634. server = next((s for s in servers if s['publicip'] == ip), None)
  635. return server
  636. def get_server_info_alt(self, gameid, session_id, client_id):
  637. """Get server by local address."""
  638. server = None
  639. ip_str = self.session_list[session_id][client_id]['addr'][0]
  640. for console in [False, True]:
  641. if server is not None:
  642. break
  643. ip = str(utils.get_ip_from_str(ip_str, console))
  644. server = self.server_manager.find_server_by_local_address(
  645. ip,
  646. self.session_list[session_id][client_id]['localaddr'],
  647. self.session_list[session_id][client_id]['gameid']
  648. )._getvalue()
  649. return server
  650. def get_server_addr(self, gameid, session_id, client_id):
  651. """Get server address."""
  652. return \
  653. self.get_server_info(gameid, session_id, client_id) or \
  654. self.get_server_info_alt(gameid, session_id, client_id)
  655. class GameSpyNatNegServer(object):
  656. def start(self):
  657. server = GameSpyNatNegUDPServer()
  658. logger.log(logging.INFO, "Server is now listening on %s:%d...",
  659. *server.server_address)
  660. server.serve_forever()
  661. if __name__ == "__main__":
  662. natneg_server = GameSpyNatNegServer()
  663. natneg_server.start()