query_gsquery.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #! /usr/bin/env python3
  2. # LinuxGSM query_gsquery.py module
  3. # Author: Daniel Gibbs
  4. # Contributors: https://linuxgsm.com/contrib
  5. # Website: https://linuxgsm.com
  6. # Description: Allows querying of various game servers.
  7. """Query game servers using a set of lightweight UDP protocols."""
  8. import argparse
  9. import socket
  10. import sys
  11. engine_types = (
  12. "protocol-valve",
  13. "protocol-quake2",
  14. "protocol-quake3",
  15. "protocol-gamespy1",
  16. "protocol-unreal2",
  17. "ut3",
  18. "minecraft",
  19. "minecraftbe",
  20. "jc2m",
  21. "mumbleping",
  22. "soldat",
  23. "teeworlds",
  24. )
  25. class GSQuery:
  26. """Game server query dispatcher."""
  27. server_response_timeout = 2
  28. default_buffer_length = 1024
  29. sourcequery = (
  30. "protocol-valve",
  31. "avalanche3.0",
  32. "barotrauma",
  33. "madness",
  34. "quakelive",
  35. "realvirtuality",
  36. "refractor",
  37. "source",
  38. "goldsrc",
  39. "spark",
  40. "starbound",
  41. "unity3d",
  42. "unreal4",
  43. "wurm",
  44. )
  45. idtech2query = ("protocol-quake2", "idtech2", "quake", "iw2.0")
  46. idtech3query = ("protocol-quake3", "iw3.0", "ioquake3", "qfusion")
  47. minecraftquery = ("minecraft", "lwjgl2")
  48. minecraftbequery = ("minecraftbe",)
  49. jc2mquery = ("jc2m",)
  50. mumblequery = ("mumbleping",)
  51. soldatquery = ("soldat",)
  52. twquery = ("teeworlds",)
  53. unrealquery = ("protocol-gamespy1", "unreal")
  54. unreal2query = ("protocol-unreal2", "unreal2")
  55. unreal3query = ("ut3", "unreal3")
  56. def __init__(self, arguments):
  57. """Create a query instance from parsed CLI args."""
  58. self.argument = arguments
  59. if self.argument.engine in self.sourcequery:
  60. self.query_prompt_string = b"\xff\xff\xff\xffTSource Engine Query\0"
  61. elif self.argument.engine in self.idtech2query:
  62. self.query_prompt_string = b"\xff\xff\xff\xffstatus\x00"
  63. elif self.argument.engine in self.idtech3query:
  64. self.query_prompt_string = b"\xff\xff\xff\xffgetstatus"
  65. elif self.argument.engine in self.jc2mquery:
  66. self.query_prompt_string = b"\xfe\xfd\x09\x10\x20\x30\x40"
  67. elif self.argument.engine in self.minecraftquery:
  68. self.query_prompt_string = b"\xfe\xfd\x09\x3d\x54\x1f\x93"
  69. elif self.argument.engine in self.minecraftbequery:
  70. self.query_prompt_string = (
  71. b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00"
  72. b"\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78"
  73. b"\x00\x00\x00\x00\x00\x00\x00\x00"
  74. )
  75. elif self.argument.engine in self.mumblequery:
  76. self.query_prompt_string = (
  77. b"\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08"
  78. )
  79. elif self.argument.engine in self.soldatquery:
  80. self.query_prompt_string = b"\x69\x00"
  81. elif self.argument.engine in self.twquery:
  82. self.query_prompt_string = b"\x04\x00\x00\xff\xff\xff\xff\x05" + bytearray(
  83. 511
  84. )
  85. elif self.argument.engine in self.unrealquery:
  86. self.query_prompt_string = b"\x5c\x69\x6e\x66\x6f\x5c"
  87. elif self.argument.engine in self.unreal2query:
  88. self.query_prompt_string = b"\x79\x00\x00\x00\x00"
  89. elif self.argument.engine in self.unreal3query:
  90. self.query_prompt_string = b"\xfe\xfd\x09\x00\x00\x00\x00"
  91. self.connected = False
  92. self.response = None
  93. @staticmethod
  94. def fatal_error(error_message, error_code=1):
  95. """Write an error message to stderr and exit."""
  96. sys.stderr.write("ERROR: " + str(error_message) + "\n")
  97. sys.exit(error_code)
  98. @staticmethod
  99. def exit_success(success_message=""):
  100. """Write a success message to stdout and exit."""
  101. sys.stdout.write("OK: " + str(success_message) + "\n")
  102. sys.exit(0)
  103. def responding(self):
  104. """Send a single UDP query and print the response."""
  105. # Connect.
  106. connection = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  107. connection.settimeout(self.server_response_timeout)
  108. try:
  109. self.connected = connection.connect(
  110. (self.argument.address, int(self.argument.port))
  111. )
  112. except socket.timeout:
  113. self.fatal_error("Request timed out", 1)
  114. except OSError:
  115. self.fatal_error("Unable to connect", 1)
  116. # Send.
  117. connection.send(self.query_prompt_string)
  118. # Receive.
  119. try:
  120. self.response = connection.recv(self.default_buffer_length)
  121. except OSError:
  122. self.fatal_error("Unable to receive", 2)
  123. connection.close()
  124. # Response.
  125. if self.response is None:
  126. self.fatal_error("No response", 3)
  127. if len(self.response) < 5:
  128. sys.exit("Short response.", 3)
  129. else:
  130. self.exit_success(str(self.response))
  131. def parse_args():
  132. """Parse command-line arguments."""
  133. parser = argparse.ArgumentParser(
  134. description="Allows querying of various game servers.",
  135. usage="usage: python3 %(prog)s [options]",
  136. add_help=False,
  137. )
  138. parser.add_argument(
  139. "-a",
  140. "--address",
  141. type=str,
  142. required=True,
  143. help="The IPv4 address of the server.",
  144. )
  145. parser.add_argument(
  146. "-p",
  147. "--port",
  148. type=int,
  149. required=True,
  150. help="The IPv4 port of the server.",
  151. )
  152. parser.add_argument(
  153. "-e",
  154. "--engine",
  155. metavar="ENGINE",
  156. choices=engine_types,
  157. help="Engine type: " + " ".join(engine_types),
  158. )
  159. parser.add_argument(
  160. "-v",
  161. "--verbose",
  162. action="store_true",
  163. help="Display verbose output.",
  164. )
  165. parser.add_argument(
  166. "-d",
  167. "--debug",
  168. action="store_true",
  169. help="Display debugging output.",
  170. )
  171. parser.add_argument(
  172. "-V",
  173. "--version",
  174. action="version",
  175. version="%(prog)s 0.0.1",
  176. help="Display version and exit.",
  177. )
  178. parser.add_argument("-h", "--help", action="help", help="Display help and exit.")
  179. return parser.parse_args()
  180. def main():
  181. """CLI entrypoint."""
  182. arguments = parse_args()
  183. server = GSQuery(arguments)
  184. server.responding()
  185. if __name__ == "__main__":
  186. main()