Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. 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 General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "NetServer.h"
21 :
22 : #include "NetClient.h"
23 : #include "NetMessage.h"
24 : #include "NetSession.h"
25 : #include "NetServerTurnManager.h"
26 : #include "NetStats.h"
27 :
28 : #include "lib/external_libraries/enet.h"
29 : #include "lib/types.h"
30 : #include "network/StunClient.h"
31 : #include "ps/CLogger.h"
32 : #include "ps/ConfigDB.h"
33 : #include "ps/GUID.h"
34 : #include "ps/Hashing.h"
35 : #include "ps/Profile.h"
36 : #include "ps/Threading.h"
37 : #include "scriptinterface/ScriptContext.h"
38 : #include "scriptinterface/ScriptInterface.h"
39 : #include "scriptinterface/JSON.h"
40 : #include "simulation2/Simulation2.h"
41 : #include "simulation2/system/TurnManager.h"
42 :
43 : #if CONFIG2_MINIUPNPC
44 : #include <miniupnpc/miniwget.h>
45 : #include <miniupnpc/miniupnpc.h>
46 : #include <miniupnpc/upnpcommands.h>
47 : #include <miniupnpc/upnperrors.h>
48 : #endif
49 :
50 : #include <string>
51 :
52 : /**
53 : * Number of peers to allocate for the enet host.
54 : * Limited by ENET_PROTOCOL_MAXIMUM_PEER_ID (4096).
55 : *
56 : * At most 8 players, 32 observers and 1 temporary connection to send the "server full" disconnect-reason.
57 : */
58 : #define MAX_CLIENTS 41
59 :
60 : #define DEFAULT_SERVER_NAME L"Unnamed Server"
61 :
62 : constexpr int CHANNEL_COUNT = 1;
63 : constexpr int FAILED_PASSWORD_TRIES_BEFORE_BAN = 3;
64 :
65 : /**
66 : * enet_host_service timeout (msecs).
67 : * Smaller numbers may hurt performance; larger numbers will
68 : * hurt latency responding to messages from game thread.
69 : */
70 : static const int HOST_SERVICE_TIMEOUT = 50;
71 :
72 : /**
73 : * Once ping goes above turn length * command delay,
74 : * the game will start 'freezing' for other clients while we catch up.
75 : * Since commands are sent client -> server -> client, divide by 2.
76 : * (duplicated in NetServer.cpp to avoid having to fetch the constants in a header file)
77 : */
78 : constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2;
79 :
80 : CNetServer* g_NetServer = NULL;
81 :
82 0 : static CStr DebugName(CNetServerSession* session)
83 : {
84 0 : if (session == NULL)
85 0 : return "[unknown host]";
86 0 : if (session->GetGUID().empty())
87 0 : return "[unauthed host]";
88 0 : return "[" + session->GetGUID().substr(0, 8) + "...]";
89 : }
90 :
91 : /**
92 : * Async task for receiving the initial game state to be forwarded to another
93 : * client that is rejoining an in-progress network game.
94 : */
95 0 : class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask
96 : {
97 : NONCOPYABLE(CNetFileReceiveTask_ServerRejoin);
98 : public:
99 0 : CNetFileReceiveTask_ServerRejoin(CNetServerWorker& server, u32 hostID)
100 0 : : m_Server(server), m_RejoinerHostID(hostID)
101 : {
102 0 : }
103 :
104 0 : virtual void OnComplete()
105 : {
106 : // We've received the game state from an existing player - now
107 : // we need to send it onwards to the newly rejoining player
108 :
109 : // Find the session corresponding to the rejoining host (if any)
110 0 : CNetServerSession* session = NULL;
111 0 : for (CNetServerSession* serverSession : m_Server.m_Sessions)
112 : {
113 0 : if (serverSession->GetHostID() == m_RejoinerHostID)
114 : {
115 0 : session = serverSession;
116 0 : break;
117 : }
118 : }
119 :
120 0 : if (!session)
121 : {
122 0 : LOGMESSAGE("Net server: rejoining client disconnected before we sent to it");
123 0 : return;
124 : }
125 :
126 : // Store the received state file, and tell the client to start downloading it from us
127 : // TODO: this will get kind of confused if there's multiple clients downloading in parallel;
128 : // they'll race and get whichever happens to be the latest received by the server,
129 : // which should still work but isn't great
130 0 : m_Server.m_JoinSyncFile = m_Buffer;
131 :
132 : // Send the init attributes alongside - these should be correct since the game should be started.
133 0 : CJoinSyncStartMessage message;
134 0 : message.m_InitAttributes = Script::StringifyJSON(ScriptRequest(m_Server.GetScriptInterface()), &m_Server.m_InitAttributes);
135 0 : session->SendMessage(&message);
136 : }
137 :
138 : private:
139 : CNetServerWorker& m_Server;
140 : u32 m_RejoinerHostID;
141 : };
142 :
143 : /*
144 : * XXX: We use some non-threadsafe functions from the worker thread.
145 : * See http://trac.wildfiregames.com/ticket/654
146 : */
147 :
148 0 : CNetServerWorker::CNetServerWorker(bool useLobbyAuth) :
149 : m_LobbyAuth(useLobbyAuth),
150 : m_Shutdown(false),
151 : m_ScriptInterface(NULL),
152 : m_NextHostID(1), m_Host(NULL), m_ControllerGUID(), m_Stats(NULL),
153 0 : m_LastConnectionCheck(0)
154 : {
155 0 : m_State = SERVER_STATE_UNCONNECTED;
156 :
157 0 : m_ServerTurnManager = NULL;
158 :
159 0 : m_ServerName = DEFAULT_SERVER_NAME;
160 0 : }
161 :
162 0 : CNetServerWorker::~CNetServerWorker()
163 : {
164 0 : if (m_State != SERVER_STATE_UNCONNECTED)
165 : {
166 : // Tell the thread to shut down
167 : {
168 0 : std::lock_guard<std::mutex> lock(m_WorkerMutex);
169 0 : m_Shutdown = true;
170 : }
171 :
172 : // Wait for it to shut down cleanly
173 0 : m_WorkerThread.join();
174 : }
175 :
176 : #if CONFIG2_MINIUPNPC
177 0 : if (m_UPnPThread.joinable())
178 0 : m_UPnPThread.detach();
179 : #endif
180 :
181 : // Clean up resources
182 :
183 0 : delete m_Stats;
184 :
185 0 : for (CNetServerSession* session : m_Sessions)
186 : {
187 0 : session->DisconnectNow(NDR_SERVER_SHUTDOWN);
188 0 : delete session;
189 : }
190 :
191 0 : if (m_Host)
192 0 : enet_host_destroy(m_Host);
193 :
194 0 : delete m_ServerTurnManager;
195 0 : }
196 :
197 0 : void CNetServerWorker::SetPassword(const CStr& hashedPassword)
198 : {
199 0 : m_Password = hashedPassword;
200 0 : }
201 :
202 :
203 0 : void CNetServerWorker::SetControllerSecret(const std::string& secret)
204 : {
205 0 : m_ControllerSecret = secret;
206 0 : }
207 :
208 :
209 0 : bool CNetServerWorker::CheckPassword(const std::string& password, const std::string& salt) const
210 : {
211 0 : return HashCryptographically(m_Password, salt) == password;
212 : }
213 :
214 :
215 0 : bool CNetServerWorker::SetupConnection(const u16 port)
216 : {
217 0 : ENSURE(m_State == SERVER_STATE_UNCONNECTED);
218 0 : ENSURE(!m_Host);
219 :
220 : // Bind to default host
221 : ENetAddress addr;
222 0 : addr.host = ENET_HOST_ANY;
223 0 : addr.port = port;
224 :
225 : // Create ENet server
226 0 : m_Host = enet_host_create(&addr, MAX_CLIENTS, CHANNEL_COUNT, 0, 0);
227 0 : if (!m_Host)
228 : {
229 0 : LOGERROR("Net server: enet_host_create failed");
230 0 : return false;
231 : }
232 :
233 0 : m_Stats = new CNetStatsTable();
234 0 : if (CProfileViewer::IsInitialised())
235 0 : g_ProfileViewer.AddRootTable(m_Stats);
236 :
237 0 : m_State = SERVER_STATE_PREGAME;
238 :
239 : // Launch the worker thread
240 0 : m_WorkerThread = std::thread(Threading::HandleExceptions<RunThread>::Wrapper, this);
241 :
242 : #if CONFIG2_MINIUPNPC
243 : // Launch the UPnP thread
244 0 : m_UPnPThread = std::thread(Threading::HandleExceptions<SetupUPnP>::Wrapper);
245 : #endif
246 :
247 0 : return true;
248 : }
249 :
250 : #if CONFIG2_MINIUPNPC
251 0 : void CNetServerWorker::SetupUPnP()
252 : {
253 0 : debug_SetThreadName("UPnP");
254 :
255 : // Values we want to set.
256 : char psPort[6];
257 0 : sprintf_s(psPort, ARRAY_SIZE(psPort), "%d", PS_DEFAULT_PORT);
258 0 : const char* leaseDuration = "0"; // Indefinite/permanent lease duration.
259 0 : const char* description = "0AD Multiplayer";
260 0 : const char* protocall = "UDP";
261 : char internalIPAddress[64];
262 : char externalIPAddress[40];
263 :
264 : // Variables to hold the values that actually get set.
265 : char intClient[40];
266 : char intPort[6];
267 : char duration[16];
268 :
269 : // Intermediate variables.
270 0 : bool allocatedUrls = false;
271 : struct UPNPUrls urls;
272 : struct IGDdatas data;
273 0 : struct UPNPDev* devlist = NULL;
274 :
275 : // Make sure everything is properly freed.
276 0 : std::function<void()> freeUPnP = [&allocatedUrls, &urls, &devlist]()
277 0 : {
278 0 : if (allocatedUrls)
279 0 : FreeUPNPUrls(&urls);
280 0 : freeUPNPDevlist(devlist);
281 : // IGDdatas does not need to be freed according to UPNP_GetIGDFromUrl
282 0 : };
283 :
284 : // Cached root descriptor URL.
285 0 : std::string rootDescURL;
286 0 : CFG_GET_VAL("network.upnprootdescurl", rootDescURL);
287 0 : if (!rootDescURL.empty())
288 0 : LOGMESSAGE("Net server: attempting to use cached root descriptor URL: %s", rootDescURL.c_str());
289 :
290 0 : int ret = 0;
291 :
292 : // Try a cached URL first
293 0 : if (!rootDescURL.empty() && UPNP_GetIGDFromUrl(rootDescURL.c_str(), &urls, &data, internalIPAddress, sizeof(internalIPAddress)))
294 : {
295 0 : LOGMESSAGE("Net server: using cached IGD = %s", urls.controlURL);
296 0 : ret = 1;
297 : }
298 : // No cached URL, or it did not respond. Try getting a valid UPnP device for 10 seconds.
299 : #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 14
300 0 : else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 2, 0)) != NULL)
301 : #else
302 : else if ((devlist = upnpDiscover(10000, 0, 0, 0, 0, 0)) != NULL)
303 : #endif
304 : {
305 0 : ret = UPNP_GetValidIGD(devlist, &urls, &data, internalIPAddress, sizeof(internalIPAddress));
306 0 : allocatedUrls = ret != 0; // urls is allocated on non-zero return values
307 : }
308 : else
309 : {
310 0 : LOGMESSAGE("Net server: upnpDiscover failed and no working cached URL.");
311 0 : freeUPnP();
312 0 : return;
313 : }
314 :
315 0 : switch (ret)
316 : {
317 0 : case 0:
318 0 : LOGMESSAGE("Net server: No IGD found");
319 0 : break;
320 0 : case 1:
321 0 : LOGMESSAGE("Net server: found valid IGD = %s", urls.controlURL);
322 0 : break;
323 0 : case 2:
324 0 : LOGMESSAGE("Net server: found a valid, not connected IGD = %s, will try to continue anyway", urls.controlURL);
325 0 : break;
326 0 : case 3:
327 0 : LOGMESSAGE("Net server: found a UPnP device unrecognized as IGD = %s, will try to continue anyway", urls.controlURL);
328 0 : break;
329 0 : default:
330 0 : debug_warn(L"Unrecognized return value from UPNP_GetValidIGD");
331 : }
332 :
333 : // Try getting our external/internet facing IP. TODO: Display this on the game-setup page for conviniance.
334 0 : ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
335 0 : if (ret != UPNPCOMMAND_SUCCESS)
336 : {
337 0 : LOGMESSAGE("Net server: GetExternalIPAddress failed with code %d (%s)", ret, strupnperror(ret));
338 0 : freeUPnP();
339 0 : return;
340 : }
341 0 : LOGMESSAGE("Net server: ExternalIPAddress = %s", externalIPAddress);
342 :
343 : // Try to setup port forwarding.
344 0 : ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, psPort, psPort,
345 : internalIPAddress, description, protocall, 0, leaseDuration);
346 0 : if (ret != UPNPCOMMAND_SUCCESS)
347 : {
348 0 : LOGMESSAGE("Net server: AddPortMapping(%s, %s, %s) failed with code %d (%s)",
349 : psPort, psPort, internalIPAddress, ret, strupnperror(ret));
350 0 : freeUPnP();
351 0 : return;
352 : }
353 :
354 : // Check that the port was actually forwarded.
355 0 : ret = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
356 : data.first.servicetype,
357 : psPort, protocall,
358 : #if defined(MINIUPNPC_API_VERSION) && MINIUPNPC_API_VERSION >= 10
359 : NULL/*remoteHost*/,
360 : #endif
361 : intClient, intPort, NULL/*desc*/,
362 : NULL/*enabled*/, duration);
363 :
364 0 : if (ret != UPNPCOMMAND_SUCCESS)
365 : {
366 0 : LOGMESSAGE("Net server: GetSpecificPortMappingEntry() failed with code %d (%s)", ret, strupnperror(ret));
367 0 : freeUPnP();
368 0 : return;
369 : }
370 :
371 0 : LOGMESSAGE("Net server: External %s:%s %s is redirected to internal %s:%s (duration=%s)",
372 : externalIPAddress, psPort, protocall, intClient, intPort, duration);
373 :
374 : // Cache root descriptor URL to try to avoid discovery next time.
375 0 : g_ConfigDB.SetValueString(CFG_USER, "network.upnprootdescurl", urls.controlURL);
376 0 : g_ConfigDB.WriteValueToFile(CFG_USER, "network.upnprootdescurl", urls.controlURL);
377 0 : LOGMESSAGE("Net server: cached UPnP root descriptor URL as %s", urls.controlURL);
378 :
379 0 : freeUPnP();
380 : }
381 : #endif // CONFIG2_MINIUPNPC
382 :
383 0 : bool CNetServerWorker::SendMessage(ENetPeer* peer, const CNetMessage* message)
384 : {
385 0 : ENSURE(m_Host);
386 :
387 0 : CNetServerSession* session = static_cast<CNetServerSession*>(peer->data);
388 :
389 0 : return CNetHost::SendMessage(message, peer, DebugName(session).c_str());
390 : }
391 :
392 0 : bool CNetServerWorker::Broadcast(const CNetMessage* message, const std::vector<NetServerSessionState>& targetStates)
393 : {
394 0 : ENSURE(m_Host);
395 :
396 0 : bool ok = true;
397 :
398 : // TODO: this does lots of repeated message serialisation if we have lots
399 : // of remote peers; could do it more efficiently if that's a real problem
400 :
401 0 : for (CNetServerSession* session : m_Sessions)
402 0 : if (std::find(targetStates.begin(), targetStates.end(), static_cast<NetServerSessionState>(session->GetCurrState())) != targetStates.end() &&
403 0 : !session->SendMessage(message))
404 0 : ok = false;
405 :
406 0 : return ok;
407 : }
408 :
409 0 : void CNetServerWorker::RunThread(CNetServerWorker* data)
410 : {
411 0 : debug_SetThreadName("NetServer");
412 :
413 0 : data->Run();
414 0 : }
415 :
416 0 : void CNetServerWorker::Run()
417 : {
418 : // The script context uses the profiler and therefore the thread must be registered before the context is created
419 0 : g_Profiler2.RegisterCurrentThread("Net server");
420 :
421 : // We create a new ScriptContext for this network thread, with a single ScriptInterface.
422 0 : std::shared_ptr<ScriptContext> netServerContext = ScriptContext::CreateContext();
423 0 : m_ScriptInterface = new ScriptInterface("Engine", "Net server", netServerContext);
424 0 : m_InitAttributes.init(m_ScriptInterface->GetGeneralJSContext(), JS::UndefinedValue());
425 :
426 : while (true)
427 : {
428 0 : if (!RunStep())
429 0 : break;
430 :
431 : // Update profiler stats
432 0 : m_Stats->LatchHostState(m_Host);
433 : }
434 :
435 : // Clear roots before deleting their context
436 0 : m_SavedCommands.clear();
437 :
438 0 : SAFE_DELETE(m_ScriptInterface);
439 0 : }
440 :
441 0 : bool CNetServerWorker::RunStep()
442 : {
443 : // Check for messages from the game thread.
444 : // (Do as little work as possible while the mutex is held open,
445 : // to avoid performance problems and deadlocks.)
446 :
447 0 : m_ScriptInterface->GetContext()->MaybeIncrementalGC(0.5f);
448 :
449 0 : ScriptRequest rq(m_ScriptInterface);
450 :
451 0 : std::vector<bool> newStartGame;
452 0 : std::vector<std::string> newGameAttributes;
453 0 : std::vector<std::pair<CStr, CStr>> newLobbyAuths;
454 0 : std::vector<u32> newTurnLength;
455 :
456 : {
457 0 : std::lock_guard<std::mutex> lock(m_WorkerMutex);
458 :
459 0 : if (m_Shutdown)
460 0 : return false;
461 :
462 0 : newStartGame.swap(m_StartGameQueue);
463 0 : newGameAttributes.swap(m_InitAttributesQueue);
464 0 : newLobbyAuths.swap(m_LobbyAuthQueue);
465 0 : newTurnLength.swap(m_TurnLengthQueue);
466 : }
467 :
468 0 : if (!newGameAttributes.empty())
469 : {
470 0 : if (m_State != SERVER_STATE_UNCONNECTED && m_State != SERVER_STATE_PREGAME)
471 0 : LOGERROR("NetServer: Init Attributes cannot be changed after the server starts loading.");
472 : else
473 : {
474 0 : JS::RootedValue gameAttributesVal(rq.cx);
475 0 : Script::ParseJSON(rq, newGameAttributes.back(), &gameAttributesVal);
476 0 : m_InitAttributes = gameAttributesVal;
477 : }
478 : }
479 :
480 0 : if (!newTurnLength.empty())
481 0 : SetTurnLength(newTurnLength.back());
482 :
483 0 : while (!newLobbyAuths.empty())
484 : {
485 0 : const std::pair<CStr, CStr>& auth = newLobbyAuths.back();
486 0 : ProcessLobbyAuth(auth.first, auth.second);
487 0 : newLobbyAuths.pop_back();
488 : }
489 :
490 : // Perform file transfers
491 0 : for (CNetServerSession* session : m_Sessions)
492 0 : session->GetFileTransferer().Poll();
493 :
494 0 : CheckClientConnections();
495 :
496 : // Process network events:
497 :
498 : ENetEvent event;
499 0 : int status = enet_host_service(m_Host, &event, HOST_SERVICE_TIMEOUT);
500 0 : if (status < 0)
501 : {
502 0 : LOGERROR("CNetServerWorker: enet_host_service failed (%d)", status);
503 : // TODO: notify game that the server has shut down
504 0 : return false;
505 : }
506 :
507 0 : if (status == 0)
508 : {
509 : // Reached timeout with no events - try again
510 0 : return true;
511 : }
512 :
513 : // Process the event:
514 :
515 0 : switch (event.type)
516 : {
517 0 : case ENET_EVENT_TYPE_CONNECT:
518 : {
519 : // Report the client address
520 0 : char hostname[256] = "(error)";
521 0 : enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
522 0 : LOGMESSAGE("Net server: Received connection from %s:%u", hostname, (unsigned int)event.peer->address.port);
523 :
524 : // Set up a session object for this peer
525 :
526 0 : CNetServerSession* session = new CNetServerSession(*this, event.peer);
527 :
528 0 : m_Sessions.push_back(session);
529 :
530 0 : SetupSession(session);
531 :
532 0 : ENSURE(event.peer->data == NULL);
533 0 : event.peer->data = session;
534 :
535 0 : HandleConnect(session);
536 :
537 0 : break;
538 : }
539 :
540 0 : case ENET_EVENT_TYPE_DISCONNECT:
541 : {
542 : // If there is an active session with this peer, then reset and delete it
543 :
544 0 : CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
545 0 : if (session)
546 : {
547 0 : LOGMESSAGE("Net server: Disconnected %s", DebugName(session).c_str());
548 :
549 : // Remove the session first, so we won't send player-update messages to it
550 : // when updating the FSM
551 0 : m_Sessions.erase(remove(m_Sessions.begin(), m_Sessions.end(), session), m_Sessions.end());
552 :
553 0 : session->Update((uint)NMT_CONNECTION_LOST, NULL);
554 :
555 0 : delete session;
556 0 : event.peer->data = NULL;
557 : }
558 :
559 0 : if (m_State == SERVER_STATE_LOADING)
560 0 : CheckGameLoadStatus(NULL);
561 :
562 0 : break;
563 : }
564 :
565 0 : case ENET_EVENT_TYPE_RECEIVE:
566 : {
567 : // If there is an active session with this peer, then process the message
568 :
569 0 : CNetServerSession* session = static_cast<CNetServerSession*>(event.peer->data);
570 0 : if (session)
571 : {
572 : // Create message from raw data
573 0 : CNetMessage* msg = CNetMessageFactory::CreateMessage(event.packet->data, event.packet->dataLength, GetScriptInterface());
574 0 : if (msg)
575 : {
576 0 : LOGMESSAGE("Net server: Received message %s of size %lu from %s", msg->ToString().c_str(), (unsigned long)msg->GetSerializedLength(), DebugName(session).c_str());
577 :
578 0 : HandleMessageReceive(msg, session);
579 :
580 0 : delete msg;
581 : }
582 : }
583 :
584 : // Done using the packet
585 0 : enet_packet_destroy(event.packet);
586 :
587 0 : break;
588 : }
589 :
590 0 : case ENET_EVENT_TYPE_NONE:
591 0 : break;
592 : }
593 :
594 0 : return true;
595 : }
596 :
597 0 : void CNetServerWorker::CheckClientConnections()
598 : {
599 : // Send messages at most once per second
600 0 : std::time_t now = std::time(nullptr);
601 0 : if (now <= m_LastConnectionCheck)
602 0 : return;
603 :
604 0 : m_LastConnectionCheck = now;
605 :
606 0 : for (size_t i = 0; i < m_Sessions.size(); ++i)
607 : {
608 0 : u32 lastReceived = m_Sessions[i]->GetLastReceivedTime();
609 0 : u32 meanRTT = m_Sessions[i]->GetMeanRTT();
610 :
611 0 : CNetMessage* message = nullptr;
612 :
613 : // Report if we didn't hear from the client since few seconds
614 0 : if (lastReceived > NETWORK_WARNING_TIMEOUT)
615 : {
616 0 : CClientTimeoutMessage* msg = new CClientTimeoutMessage();
617 0 : msg->m_GUID = m_Sessions[i]->GetGUID();
618 0 : msg->m_LastReceivedTime = lastReceived;
619 0 : message = msg;
620 : }
621 : // Report if the client has bad ping
622 0 : else if (meanRTT > NETWORK_BAD_PING)
623 : {
624 0 : CClientPerformanceMessage* msg = new CClientPerformanceMessage();
625 0 : CClientPerformanceMessage::S_m_Clients client;
626 0 : client.m_GUID = m_Sessions[i]->GetGUID();
627 0 : client.m_MeanRTT = meanRTT;
628 0 : msg->m_Clients.push_back(client);
629 0 : message = msg;
630 : }
631 :
632 : // Send to all clients except the affected one
633 : // (since that will show the locally triggered warning instead).
634 : // Also send it to clients that finished the loading screen while
635 : // the game is still waiting for other clients to finish the loading screen.
636 0 : if (message)
637 0 : for (size_t j = 0; j < m_Sessions.size(); ++j)
638 : {
639 0 : if (i != j && (
640 0 : (m_Sessions[j]->GetCurrState() == NSS_PREGAME && m_State == SERVER_STATE_PREGAME) ||
641 0 : m_Sessions[j]->GetCurrState() == NSS_INGAME))
642 : {
643 0 : m_Sessions[j]->SendMessage(message);
644 : }
645 : }
646 :
647 0 : SAFE_DELETE(message);
648 : }
649 : }
650 :
651 0 : void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServerSession* session)
652 : {
653 : // Handle non-FSM messages first
654 0 : Status status = session->GetFileTransferer().HandleMessageReceive(*message);
655 0 : if (status != INFO::SKIPPED)
656 0 : return;
657 :
658 0 : if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
659 : {
660 0 : CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
661 :
662 : // Rejoining client got our JoinSyncStart after we received the state from
663 : // another client, and has now requested that we forward it to them
664 :
665 0 : ENSURE(!m_JoinSyncFile.empty());
666 0 : session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, m_JoinSyncFile);
667 :
668 0 : return;
669 : }
670 :
671 : // Update FSM
672 0 : if (!session->Update(message->GetType(), (void*)message))
673 0 : LOGERROR("Net server: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)session->GetCurrState());
674 : }
675 :
676 0 : void CNetServerWorker::SetupSession(CNetServerSession* session)
677 : {
678 0 : void* context = session;
679 :
680 : // Set up transitions for session
681 :
682 0 : session->AddTransition(NSS_UNCONNECTED, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
683 :
684 0 : session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
685 0 : session->AddTransition(NSS_HANDSHAKE, (uint)NMT_CLIENT_HANDSHAKE, NSS_AUTHENTICATE, (void*)&OnClientHandshake, context);
686 :
687 0 : session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
688 0 : session->AddTransition(NSS_LOBBY_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
689 :
690 0 : session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED);
691 0 : session->AddTransition(NSS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NSS_PREGAME, (void*)&OnAuthenticate, context);
692 :
693 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
694 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context);
695 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_READY, NSS_PREGAME, (void*)&OnReady, context);
696 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_CLEAR_ALL_READY, NSS_PREGAME, (void*)&OnClearAllReady, context);
697 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_SETUP, NSS_PREGAME, (void*)&OnGameSetup, context);
698 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_ASSIGN_PLAYER, NSS_PREGAME, (void*)&OnAssignPlayer, context);
699 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_KICKED, NSS_PREGAME, (void*)&OnKickPlayer, context);
700 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_START, NSS_PREGAME, (void*)&OnGameStart, context);
701 0 : session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnLoadedGame, context);
702 :
703 0 : session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_KICKED, NSS_JOIN_SYNCING, (void*)&OnKickPlayer, context);
704 0 : session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
705 0 : session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnJoinSyncingLoadedGame, context);
706 :
707 0 : session->AddTransition(NSS_INGAME, (uint)NMT_REJOINED, NSS_INGAME, (void*)&OnRejoined, context);
708 0 : session->AddTransition(NSS_INGAME, (uint)NMT_KICKED, NSS_INGAME, (void*)&OnKickPlayer, context);
709 0 : session->AddTransition(NSS_INGAME, (uint)NMT_CLIENT_PAUSED, NSS_INGAME, (void*)&OnClientPaused, context);
710 0 : session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
711 0 : session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, (void*)&OnChat, context);
712 0 : session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnSimulationCommand, context);
713 0 : session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, (void*)&OnSyncCheck, context);
714 0 : session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnEndCommandBatch, context);
715 :
716 : // Set first state
717 0 : session->SetFirstState(NSS_HANDSHAKE);
718 0 : }
719 :
720 0 : bool CNetServerWorker::HandleConnect(CNetServerSession* session)
721 : {
722 0 : if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), session->GetIPAddress()) != m_BannedIPs.end())
723 : {
724 0 : session->Disconnect(NDR_BANNED);
725 0 : return false;
726 : }
727 :
728 0 : CSrvHandshakeMessage handshake;
729 0 : handshake.m_Magic = PS_PROTOCOL_MAGIC;
730 0 : handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
731 0 : handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
732 0 : return session->SendMessage(&handshake);
733 : }
734 :
735 0 : void CNetServerWorker::OnUserJoin(CNetServerSession* session)
736 : {
737 0 : AddPlayer(session->GetGUID(), session->GetUserName());
738 :
739 0 : CPlayerAssignmentMessage assignMessage;
740 0 : ConstructPlayerAssignmentMessage(assignMessage);
741 0 : session->SendMessage(&assignMessage);
742 0 : }
743 :
744 0 : void CNetServerWorker::OnUserLeave(CNetServerSession* session)
745 : {
746 0 : std::vector<CStr>::iterator pausing = std::find(m_PausingPlayers.begin(), m_PausingPlayers.end(), session->GetGUID());
747 0 : if (pausing != m_PausingPlayers.end())
748 0 : m_PausingPlayers.erase(pausing);
749 :
750 0 : RemovePlayer(session->GetGUID());
751 :
752 0 : if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
753 0 : m_ServerTurnManager->UninitialiseClient(session->GetHostID());
754 :
755 : // TODO: ought to switch the player controlled by that client
756 : // back to AI control, or something?
757 0 : }
758 :
759 0 : void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
760 : {
761 : // Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
762 0 : std::set<i32> usedIDs;
763 0 : for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
764 0 : if (p.second.m_Enabled && p.second.m_PlayerID != -1)
765 0 : usedIDs.insert(p.second.m_PlayerID);
766 :
767 : // If the player is rejoining after disconnecting, try to give them
768 : // back their old player ID. Don't do this in pregame however,
769 : // as that ID might be invalid for various reasons.
770 :
771 0 : i32 playerID = -1;
772 :
773 0 : if (m_State != SERVER_STATE_UNCONNECTED && m_State != SERVER_STATE_PREGAME)
774 : {
775 : // Try to match GUID first
776 0 : for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
777 : {
778 0 : if (!it->second.m_Enabled && it->first == guid && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
779 : {
780 0 : playerID = it->second.m_PlayerID;
781 0 : m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
782 0 : goto found;
783 : }
784 : }
785 :
786 : // Try to match username next
787 0 : for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
788 : {
789 0 : if (!it->second.m_Enabled && it->second.m_Name == name && usedIDs.find(it->second.m_PlayerID) == usedIDs.end())
790 : {
791 0 : playerID = it->second.m_PlayerID;
792 0 : m_PlayerAssignments.erase(it); // delete the old mapping, since we've got a new one now
793 0 : goto found;
794 : }
795 : }
796 : }
797 :
798 0 : found:
799 0 : PlayerAssignment assignment;
800 0 : assignment.m_Enabled = true;
801 0 : assignment.m_Name = name;
802 0 : assignment.m_PlayerID = playerID;
803 0 : assignment.m_Status = 0;
804 0 : m_PlayerAssignments[guid] = assignment;
805 :
806 : // Send the new assignments to all currently active players
807 : // (which does not include the one that's just joining)
808 0 : SendPlayerAssignments();
809 0 : }
810 :
811 0 : void CNetServerWorker::RemovePlayer(const CStr& guid)
812 : {
813 0 : m_PlayerAssignments[guid].m_Enabled = false;
814 :
815 0 : SendPlayerAssignments();
816 0 : }
817 :
818 0 : void CNetServerWorker::ClearAllPlayerReady()
819 : {
820 0 : for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
821 0 : if (p.second.m_Status != 2)
822 0 : p.second.m_Status = 0;
823 :
824 0 : SendPlayerAssignments();
825 0 : }
826 :
827 0 : void CNetServerWorker::KickPlayer(const CStrW& playerName, const bool ban)
828 : {
829 : // Find the user with that name
830 : std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
831 0 : [&](CNetServerSession* session) { return session->GetUserName() == playerName; });
832 :
833 : // and return if no one or the host has that name
834 0 : if (it == m_Sessions.end() || (*it)->GetGUID() == m_ControllerGUID)
835 0 : return;
836 :
837 0 : if (ban)
838 : {
839 : // Remember name
840 0 : if (std::find(m_BannedPlayers.begin(), m_BannedPlayers.end(), playerName) == m_BannedPlayers.end())
841 0 : m_BannedPlayers.push_back(m_LobbyAuth ? CStrW(playerName.substr(0, playerName.find(L" ("))) : playerName);
842 :
843 : // Remember IP address
844 0 : u32 ipAddress = (*it)->GetIPAddress();
845 0 : if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), ipAddress) == m_BannedIPs.end())
846 0 : m_BannedIPs.push_back(ipAddress);
847 : }
848 :
849 : // Disconnect that user
850 0 : (*it)->Disconnect(ban ? NDR_BANNED : NDR_KICKED);
851 :
852 : // Send message notifying other clients
853 0 : CKickedMessage kickedMessage;
854 0 : kickedMessage.m_Name = playerName;
855 0 : kickedMessage.m_Ban = ban;
856 0 : Broadcast(&kickedMessage, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
857 : }
858 :
859 0 : void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
860 : {
861 : // Remove anyone who's already assigned to this player
862 0 : for (std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
863 : {
864 0 : if (p.second.m_PlayerID == playerID)
865 0 : p.second.m_PlayerID = -1;
866 : }
867 :
868 : // Update this host's assignment if it exists
869 0 : if (m_PlayerAssignments.find(guid) != m_PlayerAssignments.end())
870 0 : m_PlayerAssignments[guid].m_PlayerID = playerID;
871 :
872 0 : SendPlayerAssignments();
873 0 : }
874 :
875 0 : void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message)
876 : {
877 0 : for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
878 : {
879 0 : if (!p.second.m_Enabled)
880 0 : continue;
881 :
882 0 : CPlayerAssignmentMessage::S_m_Hosts h;
883 0 : h.m_GUID = p.first;
884 0 : h.m_Name = p.second.m_Name;
885 0 : h.m_PlayerID = p.second.m_PlayerID;
886 0 : h.m_Status = p.second.m_Status;
887 0 : message.m_Hosts.push_back(h);
888 : }
889 0 : }
890 :
891 0 : void CNetServerWorker::SendPlayerAssignments()
892 : {
893 0 : CPlayerAssignmentMessage message;
894 0 : ConstructPlayerAssignmentMessage(message);
895 0 : Broadcast(&message, { NSS_PREGAME, NSS_JOIN_SYNCING, NSS_INGAME });
896 0 : }
897 :
898 0 : const ScriptInterface& CNetServerWorker::GetScriptInterface()
899 : {
900 0 : return *m_ScriptInterface;
901 : }
902 :
903 0 : void CNetServerWorker::SetTurnLength(u32 msecs)
904 : {
905 0 : if (m_ServerTurnManager)
906 0 : m_ServerTurnManager->SetTurnLength(msecs);
907 0 : }
908 :
909 0 : void CNetServerWorker::ProcessLobbyAuth(const CStr& name, const CStr& token)
910 : {
911 0 : LOGMESSAGE("Net Server: Received lobby auth message from %s with %s", name, token);
912 : // Find the user with that guid
913 : std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
914 0 : [&](CNetServerSession* session)
915 0 : { return session->GetGUID() == token; });
916 :
917 0 : if (it == m_Sessions.end())
918 0 : return;
919 :
920 0 : (*it)->SetUserName(name.FromUTF8());
921 : // Send an empty message to request the authentication message from the client
922 : // after its identity has been confirmed via the lobby
923 0 : CAuthenticateMessage emptyMessage;
924 0 : (*it)->SendMessage(&emptyMessage);
925 : }
926 :
927 0 : bool CNetServerWorker::OnClientHandshake(void* context, CFsmEvent* event)
928 : {
929 0 : ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
930 :
931 0 : CNetServerSession* session = (CNetServerSession*)context;
932 0 : CNetServerWorker& server = session->GetServer();
933 :
934 0 : CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
935 0 : if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
936 : {
937 0 : session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
938 0 : return false;
939 : }
940 :
941 0 : CStr guid = ps_generate_guid();
942 0 : int count = 0;
943 : // Ensure unique GUID
944 0 : while(std::find_if(
945 : server.m_Sessions.begin(), server.m_Sessions.end(),
946 0 : [&guid] (const CNetServerSession* session)
947 0 : { return session->GetGUID() == guid; }) != server.m_Sessions.end())
948 : {
949 0 : if (++count > 100)
950 : {
951 0 : session->Disconnect(NDR_GUID_FAILED);
952 0 : return true;
953 : }
954 0 : guid = ps_generate_guid();
955 : }
956 :
957 0 : session->SetGUID(guid);
958 :
959 0 : CSrvHandshakeResponseMessage handshakeResponse;
960 0 : handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
961 0 : handshakeResponse.m_GUID = guid;
962 0 : handshakeResponse.m_Flags = 0;
963 :
964 0 : if (server.m_LobbyAuth)
965 : {
966 0 : handshakeResponse.m_Flags |= PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH;
967 0 : session->SetNextState(NSS_LOBBY_AUTHENTICATE);
968 : }
969 :
970 0 : session->SendMessage(&handshakeResponse);
971 :
972 0 : return true;
973 : }
974 :
975 0 : bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
976 : {
977 0 : ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
978 :
979 0 : CNetServerSession* session = (CNetServerSession*)context;
980 0 : CNetServerWorker& server = session->GetServer();
981 :
982 : // Prohibit joins while the game is loading
983 0 : if (server.m_State == SERVER_STATE_LOADING)
984 : {
985 0 : LOGMESSAGE("Refused connection while the game is loading");
986 0 : session->Disconnect(NDR_SERVER_LOADING);
987 0 : return true;
988 : }
989 :
990 0 : CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
991 0 : CStrW username = SanitisePlayerName(message->m_Name);
992 0 : CStrW usernameWithoutRating(username.substr(0, username.find(L" (")));
993 :
994 : // Compare the lowercase names as specified by https://xmpp.org/extensions/xep-0029.html#sect-idm139493404168176
995 : // "[...] comparisons will be made in case-normalized canonical form."
996 0 : if (server.m_LobbyAuth && usernameWithoutRating.LowerCase() != session->GetUserName().LowerCase())
997 : {
998 0 : LOGERROR("Net server: lobby auth: %s tried joining as %s",
999 : session->GetUserName().ToUTF8(),
1000 : usernameWithoutRating.ToUTF8());
1001 0 : session->Disconnect(NDR_LOBBY_AUTH_FAILED);
1002 0 : return true;
1003 : }
1004 :
1005 : // Check the password before anything else.
1006 : // NB: m_Name must match the client's salt, @see CNetClient::SetGamePassword
1007 0 : if (!server.CheckPassword(message->m_Password, message->m_Name.ToUTF8()))
1008 : {
1009 : // Noisy logerror because players are not supposed to be able to get the IP,
1010 : // so this might be someone targeting the host for some reason
1011 : // (or TODO a dedicated server and we do want to log anyways)
1012 0 : LOGERROR("Net server: user %s tried joining with the wrong password",
1013 : session->GetUserName().ToUTF8());
1014 0 : session->Disconnect(NDR_SERVER_REFUSED);
1015 0 : return true;
1016 : }
1017 :
1018 : // Either deduplicate or prohibit join if name is in use
1019 0 : bool duplicatePlayernames = false;
1020 0 : CFG_GET_VAL("network.duplicateplayernames", duplicatePlayernames);
1021 : // If lobby authentication is enabled, the clients playername has already been registered.
1022 : // There also can't be any duplicated names.
1023 0 : if (!server.m_LobbyAuth && duplicatePlayernames)
1024 0 : username = server.DeduplicatePlayerName(username);
1025 : else
1026 : {
1027 : std::vector<CNetServerSession*>::iterator it = std::find_if(
1028 : server.m_Sessions.begin(), server.m_Sessions.end(),
1029 0 : [&username] (const CNetServerSession* session)
1030 0 : { return session->GetUserName() == username; });
1031 :
1032 0 : if (it != server.m_Sessions.end() && (*it) != session)
1033 : {
1034 0 : session->Disconnect(NDR_PLAYERNAME_IN_USE);
1035 0 : return true;
1036 : }
1037 : }
1038 :
1039 : // Disconnect banned usernames
1040 0 : if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), server.m_LobbyAuth ? usernameWithoutRating : username) != server.m_BannedPlayers.end())
1041 : {
1042 0 : session->Disconnect(NDR_BANNED);
1043 0 : return true;
1044 : }
1045 :
1046 0 : int maxObservers = 0;
1047 0 : CFG_GET_VAL("network.observerlimit", maxObservers);
1048 :
1049 0 : bool isRejoining = false;
1050 0 : bool serverFull = false;
1051 0 : if (server.m_State == SERVER_STATE_PREGAME)
1052 : {
1053 : // Don't check for maxObservers in the gamesetup, as we don't know yet who will be assigned
1054 0 : serverFull = server.m_Sessions.size() >= MAX_CLIENTS;
1055 : }
1056 : else
1057 : {
1058 0 : bool isObserver = true;
1059 0 : int disconnectedPlayers = 0;
1060 0 : int connectedPlayers = 0;
1061 : // (TODO: if GUIDs were stable, we should use them instead)
1062 0 : for (const std::pair<const CStr, PlayerAssignment>& p : server.m_PlayerAssignments)
1063 : {
1064 0 : const PlayerAssignment& assignment = p.second;
1065 :
1066 0 : if (!assignment.m_Enabled && assignment.m_Name == username)
1067 : {
1068 0 : isObserver = assignment.m_PlayerID == -1;
1069 0 : isRejoining = true;
1070 : }
1071 :
1072 0 : if (assignment.m_PlayerID == -1)
1073 0 : continue;
1074 :
1075 0 : if (assignment.m_Enabled)
1076 0 : ++connectedPlayers;
1077 : else
1078 0 : ++disconnectedPlayers;
1079 : }
1080 :
1081 : // Optionally allow everyone or only buddies to join after the game has started
1082 0 : if (!isRejoining)
1083 : {
1084 0 : CStr observerLateJoin;
1085 0 : CFG_GET_VAL("network.lateobservers", observerLateJoin);
1086 :
1087 0 : if (observerLateJoin == "everyone")
1088 : {
1089 0 : isRejoining = true;
1090 : }
1091 0 : else if (observerLateJoin == "buddies")
1092 : {
1093 0 : CStr buddies;
1094 0 : CFG_GET_VAL("lobby.buddies", buddies);
1095 0 : std::wstringstream buddiesStream(wstring_from_utf8(buddies));
1096 0 : CStrW buddy;
1097 0 : while (std::getline(buddiesStream, buddy, L','))
1098 : {
1099 0 : if (buddy == usernameWithoutRating)
1100 : {
1101 0 : isRejoining = true;
1102 0 : break;
1103 : }
1104 : }
1105 : }
1106 : }
1107 :
1108 0 : if (!isRejoining)
1109 : {
1110 0 : LOGMESSAGE("Refused connection after game start from not-previously-known user \"%s\"", utf8_from_wstring(username));
1111 0 : session->Disconnect(NDR_SERVER_ALREADY_IN_GAME);
1112 0 : return true;
1113 : }
1114 :
1115 : // Ensure all players will be able to rejoin
1116 0 : serverFull = isObserver && (
1117 0 : (int) server.m_Sessions.size() - connectedPlayers > maxObservers ||
1118 0 : (int) server.m_Sessions.size() + disconnectedPlayers >= MAX_CLIENTS);
1119 : }
1120 :
1121 0 : if (serverFull)
1122 : {
1123 0 : session->Disconnect(NDR_SERVER_FULL);
1124 0 : return true;
1125 : }
1126 :
1127 0 : u32 newHostID = server.m_NextHostID++;
1128 :
1129 0 : session->SetUserName(username);
1130 0 : session->SetHostID(newHostID);
1131 :
1132 0 : CAuthenticateResultMessage authenticateResult;
1133 0 : authenticateResult.m_Code = isRejoining ? ARC_OK_REJOINING : ARC_OK;
1134 0 : authenticateResult.m_HostID = newHostID;
1135 0 : authenticateResult.m_Message = L"Logged in";
1136 0 : authenticateResult.m_IsController = 0;
1137 :
1138 0 : if (message->m_ControllerSecret == server.m_ControllerSecret)
1139 : {
1140 0 : if (server.m_ControllerGUID.empty())
1141 : {
1142 0 : server.m_ControllerGUID = session->GetGUID();
1143 0 : authenticateResult.m_IsController = 1;
1144 : }
1145 : // TODO: we could probably handle having several controllers, or swapping?
1146 : }
1147 :
1148 0 : session->SendMessage(&authenticateResult);
1149 :
1150 0 : server.OnUserJoin(session);
1151 :
1152 0 : if (isRejoining)
1153 : {
1154 0 : ENSURE(server.m_State != SERVER_STATE_UNCONNECTED && server.m_State != SERVER_STATE_PREGAME);
1155 :
1156 : // Request a copy of the current game state from an existing player,
1157 : // so we can send it on to the new player
1158 :
1159 : // Assume session 0 is most likely the local player, so they're
1160 : // the most efficient client to request a copy from
1161 0 : CNetServerSession* sourceSession = server.m_Sessions.at(0);
1162 :
1163 0 : sourceSession->GetFileTransferer().StartTask(
1164 0 : std::shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ServerRejoin(server, newHostID))
1165 : );
1166 :
1167 0 : session->SetNextState(NSS_JOIN_SYNCING);
1168 : }
1169 :
1170 0 : return true;
1171 : }
1172 0 : bool CNetServerWorker::OnSimulationCommand(void* context, CFsmEvent* event)
1173 : {
1174 0 : ENSURE(event->GetType() == (uint)NMT_SIMULATION_COMMAND);
1175 :
1176 0 : CNetServerSession* session = (CNetServerSession*)context;
1177 0 : CNetServerWorker& server = session->GetServer();
1178 :
1179 0 : CSimulationMessage* message = (CSimulationMessage*)event->GetParamRef();
1180 :
1181 : // Ignore messages sent by one player on behalf of another player
1182 : // unless cheating is enabled
1183 0 : bool cheatsEnabled = false;
1184 0 : const ScriptInterface& scriptInterface = server.GetScriptInterface();
1185 0 : ScriptRequest rq(scriptInterface);
1186 0 : JS::RootedValue settings(rq.cx);
1187 0 : Script::GetProperty(rq, server.m_InitAttributes, "settings", &settings);
1188 0 : if (Script::HasProperty(rq, settings, "CheatsEnabled"))
1189 0 : Script::GetProperty(rq, settings, "CheatsEnabled", cheatsEnabled);
1190 :
1191 0 : PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.find(session->GetGUID());
1192 : // When cheating is disabled, fail if the player the message claims to
1193 : // represent does not exist or does not match the sender's player name
1194 0 : if (!cheatsEnabled && (it == server.m_PlayerAssignments.end() || it->second.m_PlayerID != message->m_Player))
1195 0 : return true;
1196 :
1197 : // Send it back to all clients that have finished
1198 : // the loading screen (and the synchronization when rejoining)
1199 0 : server.Broadcast(message, { NSS_INGAME });
1200 :
1201 : // Save all the received commands
1202 0 : if (server.m_SavedCommands.size() < message->m_Turn + 1)
1203 0 : server.m_SavedCommands.resize(message->m_Turn + 1);
1204 0 : server.m_SavedCommands[message->m_Turn].push_back(*message);
1205 :
1206 : // TODO: we shouldn't send the message back to the client that first sent it
1207 0 : return true;
1208 : }
1209 :
1210 0 : bool CNetServerWorker::OnSyncCheck(void* context, CFsmEvent* event)
1211 : {
1212 0 : ENSURE(event->GetType() == (uint)NMT_SYNC_CHECK);
1213 :
1214 0 : CNetServerSession* session = (CNetServerSession*)context;
1215 0 : CNetServerWorker& server = session->GetServer();
1216 :
1217 0 : CSyncCheckMessage* message = (CSyncCheckMessage*)event->GetParamRef();
1218 :
1219 0 : server.m_ServerTurnManager->NotifyFinishedClientUpdate(*session, message->m_Turn, message->m_Hash);
1220 0 : return true;
1221 : }
1222 :
1223 0 : bool CNetServerWorker::OnEndCommandBatch(void* context, CFsmEvent* event)
1224 : {
1225 0 : ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
1226 :
1227 0 : CNetServerSession* session = (CNetServerSession*)context;
1228 0 : CNetServerWorker& server = session->GetServer();
1229 :
1230 0 : CEndCommandBatchMessage* message = (CEndCommandBatchMessage*)event->GetParamRef();
1231 :
1232 : // The turn-length field is ignored
1233 0 : server.m_ServerTurnManager->NotifyFinishedClientCommands(*session, message->m_Turn);
1234 0 : return true;
1235 : }
1236 :
1237 0 : bool CNetServerWorker::OnChat(void* context, CFsmEvent* event)
1238 : {
1239 0 : ENSURE(event->GetType() == (uint)NMT_CHAT);
1240 :
1241 0 : CNetServerSession* session = (CNetServerSession*)context;
1242 0 : CNetServerWorker& server = session->GetServer();
1243 :
1244 0 : CChatMessage* message = (CChatMessage*)event->GetParamRef();
1245 :
1246 0 : message->m_GUID = session->GetGUID();
1247 :
1248 0 : server.Broadcast(message, { NSS_PREGAME, NSS_INGAME });
1249 :
1250 0 : return true;
1251 : }
1252 :
1253 0 : bool CNetServerWorker::OnReady(void* context, CFsmEvent* event)
1254 : {
1255 0 : ENSURE(event->GetType() == (uint)NMT_READY);
1256 :
1257 0 : CNetServerSession* session = (CNetServerSession*)context;
1258 0 : CNetServerWorker& server = session->GetServer();
1259 :
1260 : // Occurs if a client presses not-ready
1261 : // in the very last moment before the hosts starts the game
1262 0 : if (server.m_State == SERVER_STATE_LOADING)
1263 0 : return true;
1264 :
1265 0 : CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
1266 0 : message->m_GUID = session->GetGUID();
1267 0 : server.Broadcast(message, { NSS_PREGAME });
1268 :
1269 0 : server.m_PlayerAssignments[message->m_GUID].m_Status = message->m_Status;
1270 :
1271 0 : return true;
1272 : }
1273 :
1274 0 : bool CNetServerWorker::OnClearAllReady(void* context, CFsmEvent* event)
1275 : {
1276 0 : ENSURE(event->GetType() == (uint)NMT_CLEAR_ALL_READY);
1277 :
1278 0 : CNetServerSession* session = (CNetServerSession*)context;
1279 0 : CNetServerWorker& server = session->GetServer();
1280 :
1281 0 : if (session->GetGUID() == server.m_ControllerGUID)
1282 0 : server.ClearAllPlayerReady();
1283 :
1284 0 : return true;
1285 : }
1286 :
1287 0 : bool CNetServerWorker::OnGameSetup(void* context, CFsmEvent* event)
1288 : {
1289 0 : ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
1290 :
1291 0 : CNetServerSession* session = (CNetServerSession*)context;
1292 0 : CNetServerWorker& server = session->GetServer();
1293 :
1294 : // Changing the settings after gamestart is not implemented and would cause an Out-of-sync error.
1295 : // This happened when doubleclicking on the startgame button.
1296 0 : if (server.m_State != SERVER_STATE_PREGAME)
1297 0 : return true;
1298 :
1299 : // Only the controller is allowed to send game setup updates.
1300 : // TODO: it would be good to allow other players to request changes to some settings,
1301 : // e.g. their civilisation.
1302 : // Possibly this should use another message, to enforce a single source of truth.
1303 0 : if (session->GetGUID() == server.m_ControllerGUID)
1304 : {
1305 0 : CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
1306 0 : server.Broadcast(message, { NSS_PREGAME });
1307 : }
1308 0 : return true;
1309 : }
1310 :
1311 0 : bool CNetServerWorker::OnAssignPlayer(void* context, CFsmEvent* event)
1312 : {
1313 0 : ENSURE(event->GetType() == (uint)NMT_ASSIGN_PLAYER);
1314 0 : CNetServerSession* session = (CNetServerSession*)context;
1315 0 : CNetServerWorker& server = session->GetServer();
1316 :
1317 0 : if (session->GetGUID() == server.m_ControllerGUID)
1318 : {
1319 0 : CAssignPlayerMessage* message = (CAssignPlayerMessage*)event->GetParamRef();
1320 0 : server.AssignPlayer(message->m_PlayerID, message->m_GUID);
1321 : }
1322 0 : return true;
1323 : }
1324 :
1325 0 : bool CNetServerWorker::OnGameStart(void* context, CFsmEvent* event)
1326 : {
1327 0 : ENSURE(event->GetType() == (uint)NMT_GAME_START);
1328 0 : CNetServerSession* session = (CNetServerSession*)context;
1329 0 : CNetServerWorker& server = session->GetServer();
1330 :
1331 0 : if (session->GetGUID() != server.m_ControllerGUID)
1332 0 : return true;
1333 :
1334 0 : CGameStartMessage* message = (CGameStartMessage*)event->GetParamRef();
1335 0 : server.StartGame(message->m_InitAttributes);
1336 0 : return true;
1337 : }
1338 :
1339 0 : bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
1340 : {
1341 0 : ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
1342 :
1343 0 : CNetServerSession* loadedSession = (CNetServerSession*)context;
1344 0 : CNetServerWorker& server = loadedSession->GetServer();
1345 :
1346 : // We're in the loading state, so wait until every client has loaded
1347 : // before starting the game
1348 0 : ENSURE(server.m_State == SERVER_STATE_LOADING);
1349 0 : if (server.CheckGameLoadStatus(loadedSession))
1350 0 : return true;
1351 :
1352 0 : CClientsLoadingMessage message;
1353 : // We always send all GUIDs of clients in the loading state
1354 : // so that we don't have to bother about switching GUI pages
1355 0 : for (CNetServerSession* session : server.m_Sessions)
1356 0 : if (session->GetCurrState() != NSS_INGAME && loadedSession->GetGUID() != session->GetGUID())
1357 : {
1358 0 : CClientsLoadingMessage::S_m_Clients client;
1359 0 : client.m_GUID = session->GetGUID();
1360 0 : message.m_Clients.push_back(client);
1361 : }
1362 :
1363 : // Send to the client who has loaded the game but did not reach the NSS_INGAME state yet
1364 0 : loadedSession->SendMessage(&message);
1365 0 : server.Broadcast(&message, { NSS_INGAME });
1366 :
1367 0 : return true;
1368 : }
1369 :
1370 0 : bool CNetServerWorker::OnJoinSyncingLoadedGame(void* context, CFsmEvent* event)
1371 : {
1372 : // A client rejoining an in-progress game has now finished loading the
1373 : // map and deserialized the initial state.
1374 : // The simulation may have progressed since then, so send any subsequent
1375 : // commands to them and set them as an active player so they can participate
1376 : // in all future turns.
1377 : //
1378 : // (TODO: if it takes a long time for them to receive and execute all these
1379 : // commands, the other players will get frozen for that time and may be unhappy;
1380 : // we could try repeating this process a few times until the client converges
1381 : // on the up-to-date state, before setting them as active.)
1382 :
1383 0 : ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
1384 :
1385 0 : CNetServerSession* session = (CNetServerSession*)context;
1386 0 : CNetServerWorker& server = session->GetServer();
1387 :
1388 0 : CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();
1389 :
1390 0 : u32 turn = message->m_CurrentTurn;
1391 0 : u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn();
1392 :
1393 : // Send them all commands received since their saved state,
1394 : // and turn-ended messages for any turns that have already been processed
1395 0 : for (size_t i = turn + 1; i < std::max(readyTurn+1, (u32)server.m_SavedCommands.size()); ++i)
1396 : {
1397 0 : if (i < server.m_SavedCommands.size())
1398 0 : for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
1399 0 : session->SendMessage(&server.m_SavedCommands[i][j]);
1400 :
1401 0 : if (i <= readyTurn)
1402 : {
1403 0 : CEndCommandBatchMessage endMessage;
1404 0 : endMessage.m_Turn = i;
1405 0 : endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
1406 0 : session->SendMessage(&endMessage);
1407 : }
1408 : }
1409 :
1410 : // Tell the turn manager to expect commands from this new client
1411 : // Special case: the controller shouldn't be treated as an observer in any case.
1412 0 : bool isObserver = server.m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && server.m_ControllerGUID != session->GetGUID();
1413 0 : server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn, isObserver);
1414 :
1415 : // Tell the client that everything has finished loading and it should start now
1416 0 : CLoadedGameMessage loaded;
1417 0 : loaded.m_CurrentTurn = readyTurn;
1418 0 : session->SendMessage(&loaded);
1419 :
1420 0 : return true;
1421 : }
1422 :
1423 0 : bool CNetServerWorker::OnRejoined(void* context, CFsmEvent* event)
1424 : {
1425 : // A client has finished rejoining and the loading screen disappeared.
1426 0 : ENSURE(event->GetType() == (uint)NMT_REJOINED);
1427 :
1428 0 : CNetServerSession* session = (CNetServerSession*)context;
1429 0 : CNetServerWorker& server = session->GetServer();
1430 :
1431 : // Inform everyone of the client having rejoined
1432 0 : CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
1433 0 : message->m_GUID = session->GetGUID();
1434 0 : server.Broadcast(message, { NSS_INGAME });
1435 :
1436 : // Send all pausing players to the rejoined client.
1437 0 : for (const CStr& guid : server.m_PausingPlayers)
1438 : {
1439 0 : CClientPausedMessage pausedMessage;
1440 0 : pausedMessage.m_GUID = guid;
1441 0 : pausedMessage.m_Pause = true;
1442 0 : session->SendMessage(&pausedMessage);
1443 : }
1444 :
1445 0 : return true;
1446 : }
1447 :
1448 0 : bool CNetServerWorker::OnKickPlayer(void* context, CFsmEvent* event)
1449 : {
1450 0 : ENSURE(event->GetType() == (uint)NMT_KICKED);
1451 :
1452 0 : CNetServerSession* session = (CNetServerSession*)context;
1453 0 : CNetServerWorker& server = session->GetServer();
1454 :
1455 0 : if (session->GetGUID() == server.m_ControllerGUID)
1456 : {
1457 0 : CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
1458 0 : server.KickPlayer(message->m_Name, message->m_Ban);
1459 : }
1460 0 : return true;
1461 : }
1462 :
1463 0 : bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
1464 : {
1465 0 : ENSURE(event->GetType() == (uint)NMT_CONNECTION_LOST);
1466 :
1467 0 : CNetServerSession* session = (CNetServerSession*)context;
1468 0 : CNetServerWorker& server = session->GetServer();
1469 :
1470 0 : server.OnUserLeave(session);
1471 :
1472 0 : return true;
1473 : }
1474 :
1475 0 : bool CNetServerWorker::OnClientPaused(void* context, CFsmEvent* event)
1476 : {
1477 0 : ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
1478 :
1479 0 : CNetServerSession* session = (CNetServerSession*)context;
1480 0 : CNetServerWorker& server = session->GetServer();
1481 :
1482 0 : CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
1483 :
1484 0 : message->m_GUID = session->GetGUID();
1485 :
1486 : // Update the list of pausing players.
1487 0 : std::vector<CStr>::iterator player = std::find(server.m_PausingPlayers.begin(), server.m_PausingPlayers.end(), session->GetGUID());
1488 :
1489 0 : if (message->m_Pause)
1490 : {
1491 0 : if (player != server.m_PausingPlayers.end())
1492 0 : return true;
1493 :
1494 0 : server.m_PausingPlayers.push_back(session->GetGUID());
1495 : }
1496 : else
1497 : {
1498 0 : if (player == server.m_PausingPlayers.end())
1499 0 : return true;
1500 :
1501 0 : server.m_PausingPlayers.erase(player);
1502 : }
1503 :
1504 : // Send messages to clients that are in game, and are not the client who paused.
1505 0 : for (CNetServerSession* netSession : server.m_Sessions)
1506 0 : if (netSession->GetCurrState() == NSS_INGAME && message->m_GUID != netSession->GetGUID())
1507 0 : netSession->SendMessage(message);
1508 :
1509 0 : return true;
1510 : }
1511 :
1512 0 : bool CNetServerWorker::CheckGameLoadStatus(CNetServerSession* changedSession)
1513 : {
1514 0 : for (const CNetServerSession* session : m_Sessions)
1515 0 : if (session != changedSession && session->GetCurrState() != NSS_INGAME)
1516 0 : return false;
1517 :
1518 : // Inform clients that everyone has loaded the map and that the game can start
1519 0 : CLoadedGameMessage loaded;
1520 0 : loaded.m_CurrentTurn = 0;
1521 :
1522 : // Notice the changedSession is still in the NSS_PREGAME state
1523 0 : Broadcast(&loaded, { NSS_PREGAME, NSS_INGAME });
1524 :
1525 0 : m_State = SERVER_STATE_INGAME;
1526 0 : return true;
1527 : }
1528 :
1529 0 : void CNetServerWorker::StartGame(const CStr& initAttribs)
1530 : {
1531 0 : for (std::pair<const CStr, PlayerAssignment>& player : m_PlayerAssignments)
1532 0 : if (player.second.m_Enabled && player.second.m_PlayerID != -1 && player.second.m_Status == 0)
1533 : {
1534 0 : LOGERROR("Tried to start the game without player \"%s\" being ready!", utf8_from_wstring(player.second.m_Name).c_str());
1535 0 : return;
1536 : }
1537 :
1538 0 : m_ServerTurnManager = new CNetServerTurnManager(*this);
1539 :
1540 0 : for (CNetServerSession* session : m_Sessions)
1541 : {
1542 : // Special case: the controller shouldn't be treated as an observer in any case.
1543 0 : bool isObserver = m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && m_ControllerGUID != session->GetGUID();
1544 0 : m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0, isObserver);
1545 : }
1546 :
1547 0 : m_State = SERVER_STATE_LOADING;
1548 :
1549 : // Remove players and observers that are not present when the game starts
1550 0 : for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end();)
1551 0 : if (it->second.m_Enabled)
1552 0 : ++it;
1553 : else
1554 0 : it = m_PlayerAssignments.erase(it);
1555 :
1556 0 : SendPlayerAssignments();
1557 :
1558 : // Update init attributes. They should no longer change.
1559 0 : Script::ParseJSON(ScriptRequest(m_ScriptInterface), initAttribs, &m_InitAttributes);
1560 :
1561 0 : CGameStartMessage gameStart;
1562 0 : gameStart.m_InitAttributes = initAttribs;
1563 0 : Broadcast(&gameStart, { NSS_PREGAME });
1564 : }
1565 :
1566 0 : CStrW CNetServerWorker::SanitisePlayerName(const CStrW& original)
1567 : {
1568 0 : const size_t MAX_LENGTH = 32;
1569 :
1570 0 : CStrW name = original;
1571 0 : name.Replace(L"[", L"{"); // remove GUI tags
1572 0 : name.Replace(L"]", L"}"); // remove for symmetry
1573 :
1574 : // Restrict the length
1575 0 : if (name.length() > MAX_LENGTH)
1576 0 : name = name.Left(MAX_LENGTH);
1577 :
1578 : // Don't allow surrounding whitespace
1579 0 : name.Trim(PS_TRIM_BOTH);
1580 :
1581 : // Don't allow empty name
1582 0 : if (name.empty())
1583 0 : name = L"Anonymous";
1584 :
1585 0 : return name;
1586 : }
1587 :
1588 0 : CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
1589 : {
1590 0 : CStrW name = original;
1591 :
1592 : // Try names "Foo", "Foo (2)", "Foo (3)", etc
1593 0 : size_t id = 2;
1594 : while (true)
1595 : {
1596 0 : bool unique = true;
1597 0 : for (const CNetServerSession* session : m_Sessions)
1598 : {
1599 0 : if (session->GetUserName() == name)
1600 : {
1601 0 : unique = false;
1602 0 : break;
1603 : }
1604 : }
1605 :
1606 0 : if (unique)
1607 0 : return name;
1608 :
1609 0 : name = original + L" (" + CStrW::FromUInt(id++) + L")";
1610 0 : }
1611 : }
1612 :
1613 0 : void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
1614 : {
1615 0 : if (m_Host)
1616 0 : StunClient::SendHolePunchingMessages(*m_Host, ipStr, port);
1617 0 : }
1618 :
1619 :
1620 :
1621 :
1622 0 : CNetServer::CNetServer(bool useLobbyAuth) :
1623 0 : m_Worker(new CNetServerWorker(useLobbyAuth)),
1624 0 : m_LobbyAuth(useLobbyAuth), m_UseSTUN(false), m_PublicIp(""), m_PublicPort(20595), m_Password()
1625 : {
1626 0 : }
1627 :
1628 0 : CNetServer::~CNetServer()
1629 : {
1630 0 : delete m_Worker;
1631 0 : }
1632 :
1633 0 : bool CNetServer::GetUseSTUN() const
1634 : {
1635 0 : return m_UseSTUN;
1636 : }
1637 :
1638 0 : bool CNetServer::UseLobbyAuth() const
1639 : {
1640 0 : return m_LobbyAuth;
1641 : }
1642 :
1643 0 : bool CNetServer::SetupConnection(const u16 port)
1644 : {
1645 0 : return m_Worker->SetupConnection(port);
1646 : }
1647 :
1648 0 : CStr CNetServer::GetPublicIp() const
1649 : {
1650 0 : return m_PublicIp;
1651 : }
1652 :
1653 0 : u16 CNetServer::GetPublicPort() const
1654 : {
1655 0 : return m_PublicPort;
1656 : }
1657 :
1658 0 : u16 CNetServer::GetLocalPort() const
1659 : {
1660 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1661 0 : if (!m_Worker->m_Host)
1662 0 : return 0;
1663 0 : return m_Worker->m_Host->address.port;
1664 : }
1665 :
1666 0 : void CNetServer::SetConnectionData(const CStr& ip, const u16 port)
1667 : {
1668 0 : m_PublicIp = ip;
1669 0 : m_PublicPort = port;
1670 0 : m_UseSTUN = false;
1671 0 : }
1672 :
1673 0 : bool CNetServer::SetConnectionDataViaSTUN()
1674 : {
1675 0 : m_UseSTUN = true;
1676 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1677 0 : if (!m_Worker->m_Host)
1678 0 : return false;
1679 0 : return StunClient::FindPublicIP(*m_Worker->m_Host, m_PublicIp, m_PublicPort);
1680 : }
1681 :
1682 0 : bool CNetServer::CheckPasswordAndIncrement(const std::string& username, const std::string& password, const std::string& salt)
1683 : {
1684 0 : std::unordered_map<std::string, int>::iterator it = m_FailedAttempts.find(username);
1685 0 : if (m_Worker->CheckPassword(password, salt))
1686 : {
1687 0 : if (it != m_FailedAttempts.end())
1688 0 : it->second = 0;
1689 0 : return true;
1690 : }
1691 0 : if (it == m_FailedAttempts.end())
1692 0 : m_FailedAttempts.emplace(username, 1);
1693 : else
1694 0 : it->second++;
1695 0 : return false;
1696 : }
1697 :
1698 0 : bool CNetServer::IsBanned(const std::string& username) const
1699 : {
1700 0 : std::unordered_map<std::string, int>::const_iterator it = m_FailedAttempts.find(username);
1701 0 : return it != m_FailedAttempts.end() && it->second >= FAILED_PASSWORD_TRIES_BEFORE_BAN;
1702 : }
1703 :
1704 0 : void CNetServer::SetPassword(const CStr& password)
1705 : {
1706 0 : m_Password = password;
1707 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1708 0 : m_Worker->SetPassword(password);
1709 0 : }
1710 :
1711 0 : void CNetServer::SetControllerSecret(const std::string& secret)
1712 : {
1713 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1714 0 : m_Worker->SetControllerSecret(secret);
1715 0 : }
1716 :
1717 0 : void CNetServer::StartGame()
1718 : {
1719 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1720 0 : m_Worker->m_StartGameQueue.push_back(true);
1721 0 : }
1722 :
1723 0 : void CNetServer::UpdateInitAttributes(JS::MutableHandleValue attrs, const ScriptRequest& rq)
1724 : {
1725 : // Pass the attributes as JSON, since that's the easiest safe
1726 : // cross-thread way of passing script data
1727 0 : std::string attrsJSON = Script::StringifyJSON(rq, attrs, false);
1728 :
1729 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1730 0 : m_Worker->m_InitAttributesQueue.push_back(attrsJSON);
1731 0 : }
1732 :
1733 0 : void CNetServer::OnLobbyAuth(const CStr& name, const CStr& token)
1734 : {
1735 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1736 0 : m_Worker->m_LobbyAuthQueue.push_back(std::make_pair(name, token));
1737 0 : }
1738 :
1739 0 : void CNetServer::SetTurnLength(u32 msecs)
1740 : {
1741 0 : std::lock_guard<std::mutex> lock(m_Worker->m_WorkerMutex);
1742 0 : m_Worker->m_TurnLengthQueue.push_back(msecs);
1743 0 : }
1744 :
1745 0 : void CNetServer::SendHolePunchingMessage(const CStr& ip, u16 port)
1746 : {
1747 0 : m_Worker->SendHolePunchingMessage(ip, port);
1748 0 : }
|