proxyServer.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. from calendar import c
  2. from distutils.command.clean import clean
  3. from pickletools import bytes8
  4. import socket
  5. import re
  6. import uuid
  7. import struct
  8. from threading import Thread
  9. # server's IP address
  10. SERVER_HOST = "0.0.0.0"
  11. SERVER_PORT = 5002 # port we want to use
  12. def send_msg(sock, msg, doPack):
  13. # For 1 byte (bool) send just that
  14. # Prefix each message with a 4-byte length (network byte order)
  15. if doPack and len(msg) > 1:
  16. msg = struct.pack('<I', len(msg)) + msg
  17. sock.sendall(msg)
  18. def recv_msg(sock):
  19. # Read message length and unpack it into an integer
  20. raw_msglen = recvall(sock, 4)
  21. if not raw_msglen:
  22. return None
  23. msglen = struct.unpack('<I', raw_msglen)[0]
  24. # Read the message data
  25. return recvall(sock, msglen)
  26. def recvall(sock, n):
  27. # Helper function to recv n bytes or return None if EOF is hit
  28. data = bytearray()
  29. while len(data) < n:
  30. packet = sock.recv(n - len(data))
  31. if not packet:
  32. return None
  33. data.extend(packet)
  34. return data
  35. # initialize list/set of all connected client's sockets
  36. client_sockets = dict()
  37. class GameConnection:
  38. server: socket
  39. client: socket
  40. serverInit = False
  41. clientInit = False
  42. messageQueueIn = []
  43. messageQueueOut = []
  44. def __init__(self) -> None:
  45. self.server = None
  46. self.client = None
  47. pass
  48. class Session:
  49. total = 1
  50. joined = 0
  51. password = ""
  52. protected = False
  53. name: str
  54. host: socket
  55. host_uuid: str
  56. players = []
  57. connections = []
  58. started = False
  59. def __init__(self, host: socket, name: str) -> None:
  60. self.name = name
  61. self.host = host
  62. self.players = [host]
  63. self.joined += 1
  64. def isJoined(self, player: socket) -> bool:
  65. return player in self.players
  66. def join(self, player: socket):
  67. if not self.isJoined(player) and self.joined < self.total:
  68. self.players.append(player)
  69. self.joined += 1
  70. def leave(self, player: socket):
  71. if not self.isJoined(player) or player == self.host:
  72. return
  73. self.players.remove(player)
  74. self.joined -= 1
  75. def addConnection(self, conn: socket, isServer: bool) -> GameConnection:
  76. #find uninitialized server connection
  77. for gc in self.connections:
  78. if isServer and not gc.serverInit:
  79. gc.server = conn
  80. gc.serverInit = True
  81. return gc
  82. if not isServer and not gc.clientInit:
  83. gc.client = conn
  84. gc.clientInit = True
  85. return gc
  86. #no existing connection - create the new one
  87. gc = GameConnection()
  88. if isServer:
  89. gc.server = conn
  90. gc.serverInit = True
  91. else:
  92. gc.client = conn
  93. gc.clientInit = True
  94. self.connections.append(gc)
  95. return gc
  96. def validPipe(self, conn) -> bool:
  97. for gc in self.connections:
  98. if gc.server == conn or gc.client == conn:
  99. return gc.serverInit and gc.clientInit
  100. return False
  101. def getPipe(self, conn) -> socket:
  102. for gc in self.connections:
  103. if gc.server == conn:
  104. return gc.client
  105. if gc.client == conn:
  106. return gc.server
  107. # create a TCP socket
  108. s = socket.socket()
  109. # make the port as reusable port
  110. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  111. # bind the socket to the address we specified
  112. s.bind((SERVER_HOST, SERVER_PORT))
  113. # listen for upcoming connections
  114. s.listen(10)
  115. print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
  116. # list of active sessions
  117. sessions = dict()
  118. def handleDisconnection(client: socket):
  119. sender = client_sockets[client]
  120. if sender["joined"]:
  121. if not sender["session"].started:
  122. if sender["session"].host == client:
  123. #destroy the session, sending messages inside the function
  124. deleteSession(sender["session"])
  125. else:
  126. sender["session"].leave(client)
  127. sender["joined"] = False
  128. message = f":>>KICK:{sender['session'].name}:{sender['username']}"
  129. for client_socket in sender["session"].players:
  130. client_socket.send(message.encode())
  131. updateStatus(sender["session"])
  132. updateSessions()
  133. client.close()
  134. sender["valid"] = False
  135. def send(client: socket, message: str):
  136. sender = client_sockets[client]
  137. if "valid" not in sender or sender["valid"]:
  138. client.send(message.encode())
  139. def broadcast(clients: list, message: str):
  140. for c in clients:
  141. send(c, message)
  142. def sendSessions(client: socket):
  143. msg2 = ""
  144. counter = 0
  145. for s in sessions.values():
  146. if not s.started:
  147. msg2 += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
  148. counter += 1
  149. msg = f":>>SESSIONS:{counter}{msg2}"
  150. send(client, msg)
  151. def updateSessions():
  152. for s in client_sockets.keys():
  153. sendSessions(s)
  154. def deleteSession(session: Session):
  155. msg = f":>>KICK:{session.name}"
  156. for player in session.players:
  157. client_sockets[player]["joined"] = False
  158. msg2 = msg + f":{client_sockets[player]['username']}"
  159. send(player, msg2)
  160. sessions.pop(session.name)
  161. def updateStatus(session: Session):
  162. msg = f":>>STATUS:{session.joined}"
  163. for player in session.players:
  164. msg += f":{client_sockets[player]['username']}:{client_sockets[player]['ready']}"
  165. broadcast(session.players, msg)
  166. def startSession(session: Session):
  167. session.started = True
  168. session.host_uuid = str(uuid.uuid4())
  169. hostMessage = f":>>HOST:{session.host_uuid}:{session.joined - 1}" #one client will be connected locally
  170. #host message must be before start message
  171. send(session.host, hostMessage)
  172. for player in session.players:
  173. client_sockets[player]['uuid'] = str(uuid.uuid4())
  174. msg = f":>>START:{client_sockets[player]['uuid']}"
  175. send(player, msg)
  176. def dispatch(client: socket, sender: dict, arr: bytes):
  177. if arr == None or len(arr) == 0:
  178. return
  179. #if len(sender["prevmessages"]):
  180. # arr = sender["prevmessages"] + arr
  181. # sender["prevmessages"] = bytes()
  182. #check for game mode connection
  183. msg = str(arr)
  184. if msg.find("Aiya!") != -1:
  185. sender["pipe"] = True
  186. if sender["pipe"]:
  187. if sender["game"]:
  188. sender["prevmessages"].append(arr)
  189. else:
  190. sender["prevmessages"].append(struct.pack('<I', len(arr)) + arr)
  191. match = re.search(r"\((\w+)\)", msg)
  192. _appType = ''
  193. if match != None:
  194. _appType = match.group(1)
  195. sender["apptype"] = _appType
  196. _uuid = arr.decode()
  197. if not _uuid == '' and not sender["apptype"] == '':
  198. #search for uuid
  199. for session in sessions.values():
  200. if session.started:
  201. if _uuid.find(session.host_uuid) != -1 and sender["apptype"] == "server":
  202. gc = session.addConnection(client, True)
  203. #send_msg(gc.server, gc.messageQueueIn)
  204. #gc.messageQueueIn = bytes()
  205. sender["session"] = session
  206. sender["game"] = True
  207. #read boolean flag for the endian
  208. sender["prevmessages"].append(client.recv(1))
  209. #if not gc.clientInit:
  210. # gc.messageQueueOut += arr
  211. return
  212. if sender["apptype"] == "client":
  213. for p in session.players:
  214. if _uuid.find(client_sockets[p]["uuid"]) != -1:
  215. #client connection
  216. gc = session.addConnection(client, False)
  217. #send_msg(gc.client, gc.messageQueueOut)
  218. #gc.messageQueueOut = bytes()
  219. sender["session"] = session
  220. sender["game"] = True
  221. #read boolean flag for the endian
  222. sender["prevmessages"].append(client.recv(1))
  223. #if not gc.serverInit:
  224. # gc.messageQueueIn += arr
  225. # return
  226. break
  227. #game mode
  228. if sender["pipe"] and sender["game"] and sender["session"].validPipe(client):
  229. #send messages from queue
  230. opposite = sender["session"].getPipe(client)
  231. for x in client_sockets[opposite]["prevmessages"]:
  232. client.sendall(x)
  233. client_sockets[opposite]["prevmessages"].clear()
  234. try:
  235. for x in sender["prevmessages"]:
  236. opposite.sendall(x)
  237. except Exception as e:
  238. print(f"[!] Error: {e}")
  239. sender["prevmessages"].clear()
  240. return
  241. if sender["pipe"]:
  242. return
  243. #lobby mode
  244. msg = arr.decode()
  245. _open = msg.partition('<')
  246. _close = _open[2].partition('>')
  247. if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
  248. print(f"[!] Incorrect message from {sender['address']}: {msg}")
  249. return
  250. _nextTag = _close[2].partition('<')
  251. tag = _close[0]
  252. tag_value = _nextTag[0]
  253. #greetings to the server
  254. if tag == "GREETINGS":
  255. if sender["auth"]:
  256. print(f"[!] Greetings from authorized user {sender['username']} {sender['address']}")
  257. return
  258. print(f"[*] User {sender['address']} autorized as {tag_value}")
  259. sender["username"] = tag_value
  260. sender["auth"] = True
  261. sender["joined"] = False
  262. sendSessions(client)
  263. #VCMI version received
  264. if tag == "VER" and sender["auth"]:
  265. print(f"[*] User {sender['username']} has version {tag_value}")
  266. #message received
  267. if tag == "MSG" and sender["auth"]:
  268. message = f":>>MSG:{sender['username']}:{tag_value}"
  269. if sender["joined"]:
  270. broadcast(sender["session"].players, message)
  271. else:
  272. broadcast(client_sockets.keys(), message)
  273. #new session
  274. if tag == "NEW" and sender["auth"] and not sender["joined"]:
  275. if tag_value in sessions:
  276. #refuse creating game
  277. message = f":>>ERROR:Cannot create session with name {tag_value}, session with this name already exists"
  278. send(client, message)
  279. return
  280. sessions[tag_value] = Session(client, tag_value)
  281. sender["joined"] = True
  282. sender["ready"] = False
  283. sender["session"] = sessions[tag_value]
  284. #set password for the session
  285. if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host == client:
  286. sender["session"].password = tag_value
  287. sender["session"].protected = tag_value != ""
  288. #set amount of players to the new session
  289. if tag == "COUNT" and sender["auth"] and sender["joined"] and sender["session"].host == client:
  290. if sender["session"].total != 1:
  291. #refuse changing amount of players
  292. message = f":>>ERROR:Changing amount of players is not possible for existing session"
  293. send(client, message)
  294. return
  295. sender["session"].total = int(tag_value)
  296. message = f":>>CREATED:{sender['session'].name}"
  297. send(client, message)
  298. #now session is ready to be broadcasted
  299. message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
  300. send(client, message)
  301. updateStatus(sender["session"])
  302. updateSessions()
  303. #join session
  304. if tag == "JOIN" and sender["auth"] and not sender["joined"]:
  305. if tag_value not in sessions:
  306. message = f":>>ERROR:Session with name {tag_value} doesn't exist"
  307. send(client, message)
  308. return
  309. if sessions[tag_value].joined >= sessions[tag_value].total:
  310. message = f":>>ERROR:Session {tag_value} is full"
  311. send(client, message)
  312. return
  313. if sessions[tag_value].started:
  314. message = f":>>ERROR:Session {tag_value} is started"
  315. send(client, message)
  316. return
  317. sender["joined"] = True
  318. sender["ready"] = False
  319. sender["session"] = sessions[tag_value]
  320. if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host != client:
  321. if not sender["session"].protected or sender["session"].password == tag_value:
  322. sender["session"].join(client)
  323. message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
  324. broadcast(sender["session"].players, message)
  325. updateStatus(sender["session"])
  326. updateSessions()
  327. else:
  328. sender["joined"] = False
  329. message = f":>>ERROR:Incorrect password"
  330. send(client, message)
  331. return
  332. #leaving session
  333. if tag == "LEAVE" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
  334. if sender["session"].host == client:
  335. #destroy the session, sending messages inside the function
  336. deleteSession(sender["session"])
  337. else:
  338. message = f":>>KICK:{sender['session'].name}:{sender['username']}"
  339. broadcast(sender["session"].players, message)
  340. sender["session"].leave(client)
  341. sender["joined"] = False
  342. updateStatus(sender["session"])
  343. updateSessions()
  344. if tag == "READY" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
  345. if sender["session"].joined > 0 and sender["session"].host == client:
  346. startSession(sender["session"])
  347. updateSessions()
  348. dispatch(client, sender, (_nextTag[1] + _nextTag[2]).encode())
  349. def listen_for_client(cs):
  350. """
  351. This function keep listening for a message from `cs` socket
  352. Whenever a message is received, broadcast it to all other connected clients
  353. """
  354. while True:
  355. try:
  356. # keep listening for a message from `cs` socket
  357. if client_sockets[cs]["game"]:
  358. msg = cs.recv(4096)
  359. else:
  360. msg = recv_msg(cs)
  361. #msg = cs.recv(2048)
  362. except Exception as e:
  363. # client no longer connected
  364. print(f"[!] Error: {e}")
  365. handleDisconnection(cs)
  366. return
  367. dispatch(cs, client_sockets[cs], msg)
  368. while True:
  369. # we keep listening for new connections all the time
  370. client_socket, client_address = s.accept()
  371. print(f"[+] {client_address} connected.")
  372. # add the new connected client to connected sockets
  373. client_sockets[client_socket] = {"address": client_address, "auth": False, "username": "", "joined": False, "game": False, "pipe": False, "apptype": "", "prevmessages": []}
  374. # start a new thread that listens for each client's messages
  375. t = Thread(target=listen_for_client, args=(client_socket,))
  376. # make the thread daemon so it ends whenever the main thread ends
  377. t.daemon = True
  378. # start the thread
  379. t.start()
  380. # close client sockets
  381. for cs in client_sockets:
  382. cs.close()
  383. # close server socket
  384. s.close()