1 #include "ServerNetworking.h"
2 
3 #include "../util/Logger.h"
4 #include "../util/OptionsDB.h"
5 #include "../util/Version.h"
6 #include "../universe/ValueRefs.h"
7 #include "../parse/Parse.h"
8 
9 #include <boost/iterator/filter_iterator.hpp>
10 #include <boost/asio/high_resolution_timer.hpp>
11 #include <boost/uuid/nil_generator.hpp>
12 #include <boost/uuid/random_generator.hpp>
13 
14 #include <thread>
15 
16 using boost::asio::ip::tcp;
17 using boost::asio::ip::udp;
18 using namespace Networking;
19 
20 
21 namespace {
22     DeclareThreadSafeLogger(network);
23 }
24 
25 /** A simple server that listens for FreeOrion-server-discovery UDP datagrams
26     on the local network and sends out responses to them. */
27 class DiscoveryServer {
28 public:
DiscoveryServer(boost::asio::io_context & io_context)29     DiscoveryServer(boost::asio::io_context& io_context) :
30         m_socket(io_context)
31     {
32         // use a dual stack (ipv6 + ipv4) socket
33         udp::endpoint discovery_endpoint(udp::v6(), Networking::DiscoveryPort());
34 
35         if (GetOptionsDB().Get<bool>("singleplayer")) {
36             // when hosting a single player game only accept connections from
37             // the localhost via the loopback interface instead of the any
38             // interface.
39             // This should prevent unnecessary triggering of Desktop Firewalls as
40             // reported by various users when running single player games.
41             discovery_endpoint.address(boost::asio::ip::address_v4::loopback());
42         }
43 
44         try {
45             m_socket = udp::socket(io_context, discovery_endpoint);
46         } catch (const std::exception &e) {
47             ErrorLogger(network) << "DiscoveryServer cannot open IPv6 socket: " << e.what()
48                                  << ". Fallback to IPv4";
49             discovery_endpoint = udp::endpoint(udp::v4(), Networking::DiscoveryPort());
50             if (GetOptionsDB().Get<bool>("singleplayer"))
51                 discovery_endpoint.address(boost::asio::ip::address_v4::loopback());
52 
53             m_socket = udp::socket(io_context, discovery_endpoint);
54         }
55 
56         Listen();
57     }
58 
59 private:
Listen()60     void Listen() {
61         m_recv_buffer.fill('\0');
62         m_socket.async_receive_from(
63             boost::asio::buffer(m_recv_buffer),
64             m_remote_endpoint,
65             boost::bind(&DiscoveryServer::HandleReceive, this,
66                         boost::asio::placeholders::error));
67     }
68 
69 
HandleReceive(const boost::system::error_code & error)70     void HandleReceive(const boost::system::error_code& error) {
71         if (error) {
72             ErrorLogger(network) << "DiscoveryServer received and ignored error: " << error
73                                  << "\nfrom: " << m_remote_endpoint;
74             Listen();
75             return;
76         }
77 
78         auto message = std::string(m_recv_buffer.begin(), m_recv_buffer.end());
79         message.erase(std::find(message.begin(), message.end(), '\0'), message.end());
80         boost::trim(message);
81 
82         if (message == DISCOVERY_QUESTION) {
83             auto reply = DISCOVERY_ANSWER;
84             m_socket.send_to(
85                 boost::asio::buffer(reply),
86                 m_remote_endpoint);
87             DebugLogger(network) << "DiscoveryServer received from: " << m_remote_endpoint // operator<< outputs "IP:port"
88                                  << "\nmessage: " << message
89                                  << "\nreplied: " << reply;
90             Listen();
91             return;
92         }
93 
94         DebugLogger(network) << "DiscoveryServer evaluating FOCS expression: " << message;
95         std::string reply;
96         try {
97             if (parse::int_free_variable(message)) {
98                 auto value_ref = std::make_unique<ValueRef::Variable<int>>(ValueRef::NON_OBJECT_REFERENCE, message);
99                 reply = std::to_string(value_ref->Eval(ScriptingContext()));
100                 DebugLogger(network) << "DiscoveryServer evaluated expression as integer with result: " << reply;
101 
102             } else if (parse::double_free_variable(message)) {
103                 auto value_ref = std::make_unique<ValueRef::Variable<double>>(ValueRef::NON_OBJECT_REFERENCE, message);
104                 reply = std::to_string(value_ref->Eval(ScriptingContext()));
105                 DebugLogger(network) << "DiscoveryServer evaluated expression as double with result: " << reply;
106 
107             } else if (parse::string_free_variable(message)) {
108                 auto value_ref = std::make_unique<ValueRef::Variable<std::string>>(ValueRef::NON_OBJECT_REFERENCE, message);
109                 reply = value_ref->Eval(ScriptingContext());
110                 DebugLogger(network) << "DiscoveryServer evaluated expression as string with result: " << reply;
111 
112             //} else {
113             //    auto value_ref = std::make_unique<ValueRef::Variable<std::vector<std::string>>>(ValueRef::NON_OBJECT_REFERENCE, message);
114             //    auto result = value_ref->Eval(ScriptingContext());
115             //    for (auto entry : result)
116             //        reply += entry + "\n";
117             //    DebugLogger(network) << "DiscoveryServer evaluated expression as string vector with result: " << reply;
118 
119             } else {
120                 ErrorLogger(network) << "DiscoveryServer couldn't interpret message";
121                 reply = "FOCS ERROR";
122             }
123         } catch (...) {
124             ErrorLogger(network) << "DiscoveryServer caught exception processing message";
125             reply = "EXCEPTION ERROR";
126         }
127 
128         m_socket.send_to(boost::asio::buffer(reply), m_remote_endpoint);
129 
130         Listen();
131     }
132 
133     boost::asio::ip::udp::socket            m_socket;
134     boost::asio::ip::udp::endpoint          m_remote_endpoint;
135 
136     std::array<char, 1024> m_recv_buffer = {};
137 };
138 
139 namespace {
140     struct PlayerID {
PlayerID__anonffad127a0211::PlayerID141         PlayerID(int id) :
142             m_id(id)
143         {}
144 
operator ()__anonffad127a0211::PlayerID145         bool operator()(const PlayerConnectionPtr& player_connection)
146         { return player_connection->PlayerID() == m_id; }
147 
148     private:
149         int m_id;
150     };
151 }
152 
153 ////////////////////////////////////////////////////////////////////////////////
154 // PlayerConnection
155 ////////////////////////////////////////////////////////////////////////////////
PlayerConnection(boost::asio::io_context & io_context,MessageAndConnectionFn nonplayer_message_callback,MessageAndConnectionFn player_message_callback,ConnectionFn disconnected_callback)156 PlayerConnection::PlayerConnection(boost::asio::io_context& io_context,
157                                    MessageAndConnectionFn nonplayer_message_callback,
158                                    MessageAndConnectionFn player_message_callback,
159                                    ConnectionFn disconnected_callback) :
160     m_service(io_context),
161     m_socket(io_context),
162     m_cookie(boost::uuids::nil_uuid()),
163     m_nonplayer_message_callback(nonplayer_message_callback),
164     m_player_message_callback(player_message_callback),
165     m_disconnected_callback(disconnected_callback)
166 {}
167 
~PlayerConnection()168 PlayerConnection::~PlayerConnection() {
169     boost::system::error_code error;
170     m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
171     if (error && (m_ID != INVALID_PLAYER_ID)) {
172         if (error == boost::asio::error::eof)
173             TraceLogger(network) << "Player connection disconnected by EOF from client.";
174         else if (error == boost::asio::error::connection_reset)
175             TraceLogger(network) << "Player connection disconnected, reset by client.";
176         else if (error == boost::asio::error::operation_aborted)
177             TraceLogger(network) << "Player operation aborted by server.";
178         else if (error == boost::asio::error::shut_down)
179             TraceLogger(network) << "Player connection shutdown.";
180         else if (error == boost::asio::error::connection_aborted)
181             TraceLogger(network) << "Player connection closed by server.";
182         else if (error == boost::asio::error::not_connected)
183             TraceLogger(network) << "Player connection already down.";
184         else {
185 
186             ErrorLogger(network) << "PlayerConnection::~PlayerConnection: shutdown error #"
187                                  << error.value() << " \"" << error.message() << "\""
188                                  << " for player id " << m_ID;
189         }
190     }
191     m_socket.close();
192 }
193 
EstablishedPlayer() const194 bool PlayerConnection::EstablishedPlayer() const
195 { return m_ID != INVALID_PLAYER_ID; }
196 
PlayerID() const197 int PlayerConnection::PlayerID() const
198 { return m_ID; }
199 
PlayerName() const200 const std::string& PlayerConnection::PlayerName() const
201 { return m_player_name; }
202 
GetClientType() const203 Networking::ClientType PlayerConnection::GetClientType() const
204 { return m_client_type; }
205 
IsLocalConnection() const206 bool PlayerConnection::IsLocalConnection() const
207 { return (m_socket.remote_endpoint().address().is_loopback()); }
208 
Start()209 void PlayerConnection::Start()
210 { AsyncReadMessage(); }
211 
SendMessage(const Message & message)212 void PlayerConnection::SendMessage(const Message& message) {
213     if (!m_valid) {
214         ErrorLogger(network) << "PlayerConnection::SendMessage can't send message when not transmit connected";
215         return;
216     }
217     m_service.post(boost::bind(&PlayerConnection::SendMessageImpl, shared_from_this(), message));
218 }
219 
IsEstablished() const220 bool PlayerConnection::IsEstablished() const {
221     return (m_ID != INVALID_PLAYER_ID && !m_player_name.empty() && m_client_type != Networking::INVALID_CLIENT_TYPE);
222 }
223 
IsAuthenticated() const224 bool PlayerConnection::IsAuthenticated() const {
225     return m_authenticated;
226 }
227 
HasAuthRole(Networking::RoleType role) const228 bool PlayerConnection::HasAuthRole(Networking::RoleType role) const {
229     return m_roles.HasRole(role);
230 }
231 
Cookie() const232 boost::uuids::uuid PlayerConnection::Cookie() const
233 { return m_cookie; }
234 
AwaitPlayer(Networking::ClientType client_type,const std::string & client_version_string)235 void PlayerConnection::AwaitPlayer(Networking::ClientType client_type,
236                                    const std::string& client_version_string)
237 {
238     TraceLogger(network) << "PlayerConnection(@ " << this << ")::AwaitPlayer("
239                          << client_type << ", " << client_version_string << ")";
240     if (m_client_type != Networking::INVALID_CLIENT_TYPE) {
241         ErrorLogger(network) << "PlayerConnection::AwaitPlayer attempting to re-await an already awaiting connection.";
242         return;
243     }
244     if (client_type == Networking::INVALID_CLIENT_TYPE || client_type >= NUM_CLIENT_TYPES) {
245         ErrorLogger(network) << "PlayerConnection::EstablishPlayer passed invalid client type: " << client_type;
246         return;
247     }
248     m_client_type = client_type;
249     m_client_version_string = client_version_string;
250 }
251 
EstablishPlayer(int id,const std::string & player_name,Networking::ClientType client_type,const std::string & client_version_string)252 void PlayerConnection::EstablishPlayer(int id, const std::string& player_name, Networking::ClientType client_type,
253                                        const std::string& client_version_string)
254 {
255     TraceLogger(network) << "PlayerConnection(@ " << this << ")::EstablishPlayer("
256                          << id << ", " << player_name << ", "
257                          << client_type << ", " << client_version_string << ")";
258 
259     // ensure that this connection isn't already established
260     if (IsEstablished()) {
261         ErrorLogger(network) << "PlayerConnection::EstablishPlayer attempting to re-establish an already established connection.";
262         return;
263     }
264 
265     if (id < 0) {
266         ErrorLogger(network) << "PlayerConnection::EstablishPlayer attempting to establish a player with an invalid id: " << id;
267         return;
268     }
269     // TODO (maybe): Verify that no other players have this ID in server networking
270 
271     if (player_name.empty()) {
272         ErrorLogger(network) << "PlayerConnection::EstablishPlayer attempting to establish a player with an empty name";
273         return;
274     }
275     if (client_type == Networking::INVALID_CLIENT_TYPE || client_type >= NUM_CLIENT_TYPES) {
276         ErrorLogger(network) << "PlayerConnection::EstablishPlayer passed invalid client type: " << client_type;
277         return;
278     }
279     m_ID = id;
280     m_player_name = player_name;
281     m_client_type = client_type;
282     m_client_version_string = client_version_string;
283 }
284 
SetClientType(Networking::ClientType client_type)285 void PlayerConnection::SetClientType(Networking::ClientType client_type) {
286     m_client_type = client_type;
287     if (m_client_type == Networking::INVALID_CLIENT_TYPE)
288         ErrorLogger(network) << "PlayerConnection client type set to INVALID_CLIENT_TYPE...?";
289 }
290 
SetAuthenticated()291 void PlayerConnection::SetAuthenticated() {
292     m_authenticated = true;
293 }
294 
SetAuthRoles(const std::initializer_list<Networking::RoleType> & roles)295 void PlayerConnection::SetAuthRoles(const std::initializer_list<Networking::RoleType>& roles) {
296     m_roles = Networking::AuthRoles(roles);
297     SendMessage(SetAuthorizationRolesMessage(m_roles));
298 }
299 
SetAuthRoles(const Networking::AuthRoles & roles)300 void PlayerConnection::SetAuthRoles(const Networking::AuthRoles& roles) {
301     m_roles = roles;
302     SendMessage(SetAuthorizationRolesMessage(m_roles));
303 }
304 
SetAuthRole(Networking::RoleType role,bool value)305 void PlayerConnection::SetAuthRole(Networking::RoleType role, bool value) {
306     m_roles.SetRole(role, value);
307     SendMessage(SetAuthorizationRolesMessage(m_roles));
308 }
309 
SetCookie(boost::uuids::uuid cookie)310 void PlayerConnection::SetCookie(boost::uuids::uuid cookie)
311 { m_cookie = cookie; }
312 
ClientVersionString() const313 const std::string& PlayerConnection::ClientVersionString() const
314 { return m_client_version_string; }
315 
IsBinarySerializationUsed() const316 bool PlayerConnection::IsBinarySerializationUsed() const {
317     return GetOptionsDB().Get<bool>("network.server.binary.enabled")
318         && !m_client_version_string.empty()
319         && m_client_version_string == FreeOrionVersionString();
320 }
321 
NewConnection(boost::asio::io_context & io_context,MessageAndConnectionFn nonplayer_message_callback,MessageAndConnectionFn player_message_callback,ConnectionFn disconnected_callback)322 PlayerConnectionPtr PlayerConnection::NewConnection(boost::asio::io_context& io_context,
323                                                     MessageAndConnectionFn nonplayer_message_callback,
324                                                     MessageAndConnectionFn player_message_callback,
325                                                     ConnectionFn disconnected_callback)
326 {
327     return PlayerConnectionPtr(
328         new PlayerConnection(io_context, nonplayer_message_callback, player_message_callback,
329                              disconnected_callback));
330 }
331 
332 namespace {
MessageTypeName(Message::MessageType type)333     std::string MessageTypeName(Message::MessageType type) {
334         switch (type) {
335         case Message::UNDEFINED:                return "Undefined";
336         case Message::DEBUG:                    return "Debug";
337         case Message::ERROR_MSG:                return "Error";
338         case Message::HOST_SP_GAME:             return "Host SP Game";
339         case Message::HOST_MP_GAME:             return "Host MP Game";
340         case Message::JOIN_GAME:                return "Join Game";
341         case Message::HOST_ID:                  return "Host ID";
342         case Message::LOBBY_UPDATE:             return "Lobby Update";
343         case Message::LOBBY_EXIT:               return "Lobby Exit";
344         case Message::START_MP_GAME:            return "Start MP Game";
345         case Message::SAVE_GAME_INITIATE:       return "Save Game";
346         case Message::SAVE_GAME_COMPLETE:       return "Save Game";
347         case Message::LOAD_GAME:                return "Load Game";
348         case Message::GAME_START:               return "Game Start";
349         case Message::TURN_UPDATE:              return "Turn Update";
350         case Message::TURN_PARTIAL_UPDATE:      return "Turn Partial Update";
351         case Message::TURN_ORDERS:              return "Turn Orders";
352         case Message::TURN_PROGRESS:            return "Turn Progress";
353         case Message::PLAYER_STATUS:            return "Player Status";
354         case Message::PLAYER_CHAT:              return "Player Chat";
355         case Message::DIPLOMACY:                return "Diplomacy";
356         case Message::DIPLOMATIC_STATUS:        return "Diplomatic Status";
357         case Message::REQUEST_NEW_OBJECT_ID:    return "Request New Object ID";
358         case Message::DISPATCH_NEW_OBJECT_ID:   return "Dispatch New Object ID";
359         case Message::REQUEST_NEW_DESIGN_ID:    return "Request New Design ID";
360         case Message::DISPATCH_NEW_DESIGN_ID:   return "Dispatch New Design ID";
361         case Message::END_GAME:                 return "End Game";
362         case Message::AI_END_GAME_ACK:          return "Acknowledge Shut Down Server";
363         case Message::MODERATOR_ACTION:         return "Moderator Action";
364         case Message::SHUT_DOWN_SERVER:         return "Shut Down Server";
365         case Message::REQUEST_SAVE_PREVIEWS:    return "Request save previews";
366         case Message::DISPATCH_SAVE_PREVIEWS:   return "Dispatch save previews";
367         case Message::REQUEST_COMBAT_LOGS:      return "Request combat logs";
368         case Message::DISPATCH_COMBAT_LOGS:     return "Dispatch combat logs";
369         case Message::LOGGER_CONFIG:            return "Logger config";
370         case Message::CHECKSUM:                 return "Checksum";
371         case Message::AUTH_REQUEST:             return "Authentication request";
372         case Message::AUTH_RESPONSE:            return "Authentication response";
373         case Message::CHAT_HISTORY:             return "Chat history";
374         case Message::SET_AUTH_ROLES:           return "Set authorization roles";
375         case Message::ELIMINATE_SELF:           return "Eliminate self";
376         case Message::UNREADY:                  return "Unready";
377         default:                                return std::string("Unknown Type(") + std::to_string(static_cast<int>(type)) + ")";
378         };
379     }
380 }
381 
HandleMessageBodyRead(boost::system::error_code error,std::size_t bytes_transferred)382 void PlayerConnection::HandleMessageBodyRead(boost::system::error_code error,
383                                              std::size_t bytes_transferred)
384 {
385     if (error) {
386         if (error == boost::asio::error::eof ||
387             error == boost::asio::error::connection_reset) {
388             ErrorLogger(network) << "PlayerConnection::HandleMessageBodyRead(): "
389                                  << "error #" << error.value() << " \"" << error.message() << "\"";
390             EventSignal(boost::bind(m_disconnected_callback, shared_from_this()));
391         } else {
392             ErrorLogger(network) << "PlayerConnection::HandleMessageBodyRead(): "
393                                  << "error #" << error.value() << " \"" << error.message() << "\"";
394         }
395     } else {
396         assert(static_cast<int>(bytes_transferred) <= m_incoming_header_buffer[Message::Parts::SIZE]);
397         if (static_cast<int>(bytes_transferred) == m_incoming_header_buffer[Message::Parts::SIZE]) {
398             if (m_incoming_message.Type() != Message::REQUEST_NEW_DESIGN_ID) {   // new design id messages ignored due to log spam
399                 TraceLogger(network) << "Server received message from player id: " << m_ID
400                                      << " of type " << MessageTypeName(m_incoming_message.Type())
401                                      << " and size " << m_incoming_message.Size();
402                 //TraceLogger(network) << "     Full message: " << m_incoming_message;
403             }
404             if (EstablishedPlayer()) {
405                 EventSignal(boost::bind(m_player_message_callback, m_incoming_message, shared_from_this()));
406             } else {
407                 EventSignal(boost::bind(m_nonplayer_message_callback, m_incoming_message, shared_from_this()));
408             }
409             m_incoming_message = Message();
410             AsyncReadMessage();
411         }
412     }
413 }
414 
HandleMessageHeaderRead(boost::system::error_code error,std::size_t bytes_transferred)415 void PlayerConnection::HandleMessageHeaderRead(boost::system::error_code error,
416                                                std::size_t bytes_transferred)
417 {
418     if (error) {
419         // HACK! This entire m_new_connection case should not be needed as far
420         // as I can see.  It is here because without it, there are
421         // intermittent problems, like the server gets a disconnect event for
422         // each connection, just after the connection.  I cannot figure out
423         // why this is so, but putting a pause in place seems to at least
424         // mask the problem.  For now, this is sufficient, since rapid
425         // connects and disconnects are not a priority.
426         if (m_new_connection && error != boost::asio::error::eof) {
427             ErrorLogger(network) << "PlayerConnection::HandleMessageHeaderRead(): "
428                                  << "new connection error #" << error.value() << " \""
429                                  << error.message() << "\"" << " waiting for 0.5s";
430             // wait half a second if the first data read is an error; we
431             // probably just need more setup time
432             std::this_thread::sleep_for(std::chrono::milliseconds(500));
433             m_new_connection = false;
434             if (m_socket.is_open()) {
435                 ErrorLogger(network) << "Spurious network error on startup of client. player id = "
436                                      << m_ID << ".  Retrying read...";
437                 AsyncReadMessage();
438             } else {
439                 ErrorLogger(network) << "Network connection for client failed on startup. "
440                                      << "player id = " << m_ID << "";
441             }
442         } else {
443             if (error == boost::asio::error::eof ||
444                 error == boost::asio::error::connection_reset ||
445                 error == boost::asio::error::timed_out)
446             {
447                 EventSignal(boost::bind(m_disconnected_callback, shared_from_this()));
448             } else {
449                 ErrorLogger(network) << "PlayerConnection::HandleMessageHeaderRead(): "
450                                      << "error #" << error.value() << " \"" << error.message() << "\"";
451             }
452         }
453     } else {
454         m_new_connection = false;
455         assert(bytes_transferred <= Message::HeaderBufferSize);
456         if (bytes_transferred == Message::HeaderBufferSize) {
457             BufferToHeader(m_incoming_header_buffer, m_incoming_message);
458             auto msg_size = m_incoming_header_buffer[Message::Parts::SIZE];
459             if (GetOptionsDB().Get<int>("network.server.client-message-size.max") > 0 &&
460                 msg_size > GetOptionsDB().Get<int>("network.server.client-message-size.max"))
461             {
462                 ErrorLogger(network) << "PlayerConnection::HandleMessageHeaderRead(): "
463                                      << "too big message " << msg_size << " bytes ";
464                 boost::system::error_code error;
465                 m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
466                 m_socket.close();
467                 return;
468             }
469             m_incoming_message.Resize(m_incoming_header_buffer[Message::Parts::SIZE]);
470             boost::asio::async_read(
471                 m_socket,
472                 boost::asio::buffer(m_incoming_message.Data(), m_incoming_message.Size()),
473                 boost::bind(&PlayerConnection::HandleMessageBodyRead, shared_from_this(),
474                             boost::asio::placeholders::error,
475                             boost::asio::placeholders::bytes_transferred));
476         }
477     }
478 }
479 
AsyncReadMessage()480 void PlayerConnection::AsyncReadMessage() {
481     boost::asio::async_read(m_socket, boost::asio::buffer(m_incoming_header_buffer),
482                             boost::bind(&PlayerConnection::HandleMessageHeaderRead,
483                                         shared_from_this(),
484                                         boost::asio::placeholders::error,
485                                         boost::asio::placeholders::bytes_transferred));
486 }
487 
SendMessageImpl(PlayerConnectionPtr self,Message message)488 void PlayerConnection::SendMessageImpl(PlayerConnectionPtr self, Message message) {
489     bool start_write = self->m_outgoing_messages.empty();
490     self->m_outgoing_messages.push_back(Message());
491     swap(self->m_outgoing_messages.back(), message);
492     if (start_write)
493         self->AsyncWriteMessage();
494 }
495 
AsyncWriteMessage()496 void PlayerConnection::AsyncWriteMessage() {
497     if (!m_valid) {
498         ErrorLogger(network) << "PlayerConnection::AsyncWriteMessage(): player id = " << m_ID
499                              << ". Socket is closed. Dropping message.";
500         return;
501     }
502 
503     HeaderToBuffer(m_outgoing_messages.front(), m_outgoing_header);
504     std::vector<boost::asio::const_buffer> buffers;
505     buffers.push_back(boost::asio::buffer(m_outgoing_header));
506     buffers.push_back(boost::asio::buffer(m_outgoing_messages.front().Data(),
507                                           m_outgoing_messages.front().Size()));
508     boost::asio::async_write(m_socket, buffers,
509                              boost::bind(&PlayerConnection::HandleMessageWrite, shared_from_this(),
510                                          boost::asio::placeholders::error,
511                                          boost::asio::placeholders::bytes_transferred));
512 }
513 
HandleMessageWrite(PlayerConnectionPtr self,boost::system::error_code error,std::size_t bytes_transferred)514 void PlayerConnection::HandleMessageWrite(PlayerConnectionPtr self,
515                                           boost::system::error_code error,
516                                           std::size_t bytes_transferred)
517 {
518     if (error) {
519         self->m_valid = false;
520         ErrorLogger(network) << "PlayerConnection::AsyncWriteMessage(): player id = " << self->m_ID
521                              << " error #" << error.value() << " \"" << error.message() << "\"";
522         boost::asio::high_resolution_timer t(self->m_service);
523         t.async_wait(boost::bind(&PlayerConnection::AsyncErrorHandler, self, error, boost::asio::placeholders::error));
524         return;
525     }
526 
527     if (static_cast<int>(bytes_transferred) != static_cast<int>(Message::HeaderBufferSize) + self->m_outgoing_header[Message::Parts::SIZE])
528         return;
529 
530     self->m_outgoing_messages.pop_front();
531     if (!self->m_outgoing_messages.empty())
532         self->AsyncWriteMessage();
533 }
534 
AsyncErrorHandler(PlayerConnectionPtr self,boost::system::error_code handled_error,boost::system::error_code error)535 void PlayerConnection::AsyncErrorHandler(PlayerConnectionPtr self, boost::system::error_code handled_error,
536                                          boost::system::error_code error)
537 { self->EventSignal(boost::bind(self->m_disconnected_callback, self)); }
538 
539 
540 ////////////////////////////////////////////////////////////////////////////////
541 // ServerNetworking
542 ////////////////////////////////////////////////////////////////////////////////
operator ()(const PlayerConnectionPtr & player_connection) const543 bool ServerNetworking::EstablishedPlayer::operator()(
544     const PlayerConnectionPtr& player_connection) const
545 { return player_connection->EstablishedPlayer(); }
546 
ServerNetworking(boost::asio::io_context & io_context,MessageAndConnectionFn nonplayer_message_callback,MessageAndConnectionFn player_message_callback,ConnectionFn disconnected_callback)547 ServerNetworking::ServerNetworking(boost::asio::io_context& io_context,
548                                    MessageAndConnectionFn nonplayer_message_callback,
549                                    MessageAndConnectionFn player_message_callback,
550                                    ConnectionFn disconnected_callback) :
551     m_host_player_id(Networking::INVALID_PLAYER_ID),
552     m_discovery_server(new DiscoveryServer(io_context)),
553     m_player_connection_acceptor(io_context),
554     m_nonplayer_message_callback(nonplayer_message_callback),
555     m_player_message_callback(player_message_callback),
556     m_disconnected_callback(disconnected_callback)
557 { Init(); }
558 
~ServerNetworking()559 ServerNetworking::~ServerNetworking()
560 { delete m_discovery_server; }
561 
empty() const562 bool ServerNetworking::empty() const
563 { return m_player_connections.empty(); }
564 
size() const565 std::size_t ServerNetworking::size() const
566 { return m_player_connections.size(); }
567 
begin() const568 ServerNetworking::const_iterator ServerNetworking::begin() const
569 { return m_player_connections.begin(); }
570 
end() const571 ServerNetworking::const_iterator ServerNetworking::end() const
572 { return m_player_connections.end(); }
573 
NumEstablishedPlayers() const574 std::size_t ServerNetworking::NumEstablishedPlayers() const
575 { return std::distance(established_begin(), established_end()); }
576 
GetPlayer(int id) const577 ServerNetworking::const_established_iterator ServerNetworking::GetPlayer(int id) const
578 { return std::find_if(established_begin(), established_end(), PlayerID(id)); }
579 
established_begin() const580 ServerNetworking::const_established_iterator ServerNetworking::established_begin() const {
581     return const_established_iterator(EstablishedPlayer(),
582                                       m_player_connections.begin(),
583                                       m_player_connections.end());
584 }
585 
established_end() const586 ServerNetworking::const_established_iterator ServerNetworking::established_end() const {
587     return const_established_iterator(EstablishedPlayer(),
588                                       m_player_connections.end(),
589                                       m_player_connections.end());
590 }
591 
NewPlayerID() const592 int ServerNetworking::NewPlayerID() const {
593     int biggest_current_player_id(0);
594     for (const PlayerConnectionPtr player : m_player_connections) {
595         int player_id = player->PlayerID();
596         if (player_id != INVALID_PLAYER_ID && player_id > biggest_current_player_id)
597             biggest_current_player_id = player_id;
598     }
599     return biggest_current_player_id + 1;
600 }
601 
HostPlayerID() const602 int ServerNetworking::HostPlayerID() const
603 { return m_host_player_id; }
604 
PlayerIsHost(int player_id) const605 bool ServerNetworking::PlayerIsHost(int player_id) const {
606     if (player_id == Networking::INVALID_PLAYER_ID)
607         return false;
608     return player_id == m_host_player_id;
609 }
610 
ModeratorsInGame() const611 bool ServerNetworking::ModeratorsInGame() const {
612     for (const PlayerConnectionPtr player : m_player_connections) {
613         if (player->GetClientType() == Networking::CLIENT_TYPE_HUMAN_MODERATOR)
614             return true;
615     }
616     return false;
617 }
618 
IsAvailableNameInCookies(const std::string & player_name) const619 bool ServerNetworking::IsAvailableNameInCookies(const std::string& player_name) const {
620     boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
621     for (const auto& cookie : m_cookies) {
622         if (cookie.second.expired >= now && cookie.second.player_name == player_name)
623             return false;
624     }
625     return true;
626 }
627 
CheckCookie(boost::uuids::uuid cookie,const std::string & player_name,Networking::AuthRoles & roles,bool & authenticated) const628 bool ServerNetworking::CheckCookie(boost::uuids::uuid cookie,
629                                    const std::string& player_name,
630                                    Networking::AuthRoles& roles,
631                                    bool& authenticated) const
632 {
633     if (cookie.is_nil())
634         return false;
635 
636     auto it = m_cookies.find(cookie);
637     if (it != m_cookies.end() && player_name == it->second.player_name) {
638         boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
639         if (it->second.expired >= now) {
640             roles = it->second.roles;
641             authenticated = it->second.authenticated;
642             return true;
643         }
644     }
645     return false;
646 }
647 
GetCookiesSize() const648 int ServerNetworking::GetCookiesSize() const
649 { return m_cookies.size(); }
650 
SendMessageAll(const Message & message)651 void ServerNetworking::SendMessageAll(const Message& message) {
652     for (auto player_it = established_begin();
653         player_it != established_end(); ++player_it)
654     {
655         (*player_it)->SendMessage(message);
656     }
657 }
658 
Disconnect(int id)659 void ServerNetworking::Disconnect(int id) {
660     established_iterator it = GetPlayer(id);
661     if (it == established_end()) {
662         ErrorLogger(network) << "ServerNetworking::Disconnect couldn't find player with id " << id << " to disconnect.  aborting";
663         return;
664     }
665     PlayerConnectionPtr player = *it;
666     if (player->PlayerID() != id) {
667         ErrorLogger(network) << "ServerNetworking::Disconnect got PlayerConnectionPtr with inconsistent player id (" << player->PlayerID() << ") to what was requrested (" << id << ")";
668         return;
669     }
670     Disconnect(player);
671 }
672 
Disconnect(PlayerConnectionPtr player_connection)673 void ServerNetworking::Disconnect(PlayerConnectionPtr player_connection)
674 {
675     TraceLogger(network) << "ServerNetworking::Disconnect";
676     DisconnectImpl(player_connection);
677 }
678 
DisconnectAll()679 void ServerNetworking::DisconnectAll() {
680     TraceLogger(network) << "ServerNetworking::DisconnectAll";
681     for (const_iterator it = m_player_connections.begin();
682          it != m_player_connections.end(); ) {
683         PlayerConnectionPtr player_connection = *it++;
684         DisconnectImpl(player_connection);
685     }
686 }
687 
begin()688 ServerNetworking::iterator ServerNetworking::begin()
689 { return m_player_connections.begin(); }
690 
end()691 ServerNetworking::iterator ServerNetworking::end()
692 { return m_player_connections.end(); }
693 
GetPlayer(int id)694 ServerNetworking::established_iterator ServerNetworking::GetPlayer(int id)
695 { return std::find_if(established_begin(), established_end(), PlayerID(id)); }
696 
established_begin()697 ServerNetworking::established_iterator ServerNetworking::established_begin() {
698     return established_iterator(EstablishedPlayer(),
699                                 m_player_connections.begin(),
700                                 m_player_connections.end());
701 }
702 
established_end()703 ServerNetworking::established_iterator ServerNetworking::established_end() {
704     return established_iterator(EstablishedPlayer(),
705                                 m_player_connections.end(),
706                                 m_player_connections.end());
707 }
708 
HandleNextEvent()709 void ServerNetworking::HandleNextEvent() {
710     if (!m_event_queue.empty()) {
711         std::function<void ()> f = m_event_queue.front();
712         m_event_queue.pop();
713         f();
714     }
715 }
716 
SetHostPlayerID(int host_player_id)717 void ServerNetworking::SetHostPlayerID(int host_player_id)
718 { m_host_player_id = host_player_id; }
719 
GenerateCookie(const std::string & player_name,const Networking::AuthRoles & roles,bool authenticated)720 boost::uuids::uuid ServerNetworking::GenerateCookie(const std::string& player_name,
721                                                     const Networking::AuthRoles& roles,
722                                                     bool authenticated)
723 {
724     boost::uuids::uuid cookie = boost::uuids::random_generator()();
725     m_cookies.erase(cookie); // remove previous cookie if exists
726     m_cookies.emplace(cookie, CookieData(player_name,
727                                          boost::posix_time::second_clock::local_time() +
728                                              boost::posix_time::minutes(GetOptionsDB().Get<int>("network.server.cookies.expire-minutes")),
729                                          roles,
730                                          authenticated));
731     return cookie;
732 }
733 
UpdateCookie(boost::uuids::uuid cookie)734 void ServerNetworking::UpdateCookie(boost::uuids::uuid cookie) {
735     if (cookie.is_nil())
736         return;
737 
738     auto it = m_cookies.find(cookie);
739     if (it != m_cookies.end()) {
740         it->second.expired = boost::posix_time::second_clock::local_time() +
741             boost::posix_time::minutes(GetOptionsDB().Get<int>("network.server.cookies.expire-minutes"));
742     }
743 }
744 
CleanupCookies()745 void ServerNetworking::CleanupCookies() {
746     std::unordered_set<boost::uuids::uuid, boost::hash<boost::uuids::uuid>> to_delete;
747     boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
748     // clean up expired cookies
749     for (const auto& cookie : m_cookies) {
750         if (cookie.second.expired < now)
751             to_delete.insert(cookie.first);
752     }
753     // don't clean up cookies from active connections
754     for (auto it = established_begin();
755         it != established_end(); ++it)
756     {
757         to_delete.erase((*it)->Cookie());
758     }
759     for (auto cookie : to_delete)
760         m_cookies.erase(cookie);
761 }
762 
Init()763 void ServerNetworking::Init() {
764     // use a dual stack (ipv6 + ipv4) socket
765     tcp::endpoint message_endpoint(tcp::v6(), Networking::MessagePort());
766 
767     if (GetOptionsDB().Get<bool>("singleplayer")) {
768         // when hosting a single player game only accept connections from
769         // the localhost via the loopback interface instead of the any
770         // interface.
771         // This should prevent unnecessary triggering of Desktop Firewalls as
772         // reported by various users when running single player games.
773         message_endpoint.address(boost::asio::ip::address_v4::loopback());
774     }
775 
776     try {
777         m_player_connection_acceptor.open(message_endpoint.protocol());
778     } catch (const std::exception &e) {
779         ErrorLogger(network) << "Server cannot open IPv6 socket: " << e.what()
780                              << ". Fallback to IPv4";
781         message_endpoint = tcp::endpoint(tcp::v4(), Networking::MessagePort());
782         if (GetOptionsDB().Get<bool>("singleplayer"))
783             message_endpoint.address(boost::asio::ip::address_v4::loopback());
784 
785         m_player_connection_acceptor.open(message_endpoint.protocol());
786     }
787     m_player_connection_acceptor.set_option(
788         boost::asio::socket_base::reuse_address(true));
789     if (message_endpoint.protocol() == boost::asio::ip::tcp::v6())
790         m_player_connection_acceptor.set_option(
791             boost::asio::ip::v6_only(false));  // may be true by default on some systems
792     m_player_connection_acceptor.set_option(
793         boost::asio::socket_base::linger(true, SOCKET_LINGER_TIME));
794     m_player_connection_acceptor.bind(message_endpoint);
795     m_player_connection_acceptor.listen();
796 
797     AcceptNextMessagingConnection();
798 }
799 
AcceptNextMessagingConnection()800 void ServerNetworking::AcceptNextMessagingConnection() {
801 #if BOOST_VERSION >= 106000
802     using boost::placeholders::_1;
803 #endif
804 
805     auto next_connection = PlayerConnection::NewConnection(
806 #if BOOST_VERSION >= 106600
807         m_player_connection_acceptor.get_executor().context(),
808 #else
809         m_player_connection_acceptor.get_io_service(),
810 #endif
811         m_nonplayer_message_callback,
812         m_player_message_callback,
813         boost::bind(&ServerNetworking::DisconnectImpl, this, _1));
814     next_connection->EventSignal.connect(
815         boost::bind(&ServerNetworking::EnqueueEvent, this, _1));
816     m_player_connection_acceptor.async_accept(
817         next_connection->m_socket,
818         boost::bind(&ServerNetworking::AcceptPlayerMessagingConnection,
819                     this,
820                     next_connection,
821                     boost::asio::placeholders::error));
822 }
823 
AcceptPlayerMessagingConnection(PlayerConnectionPtr player_connection,const boost::system::error_code & error)824 void ServerNetworking::AcceptPlayerMessagingConnection(PlayerConnectionPtr player_connection,
825                                                        const boost::system::error_code& error)
826 {
827     if (!error) {
828         TraceLogger(network) << "ServerNetworking::AcceptPlayerMessagingConnection : connected to new player";
829         m_player_connections.insert(player_connection);
830         player_connection->Start();
831         AcceptNextMessagingConnection();
832     } else {
833         throw error;
834     }
835 }
836 
DisconnectImpl(PlayerConnectionPtr player_connection)837 void ServerNetworking::DisconnectImpl(PlayerConnectionPtr player_connection) {
838     TraceLogger(network) << "ServerNetworking::DisconnectImpl : disconnecting player "
839                          << player_connection->PlayerID();
840     m_player_connections.erase(player_connection);
841     m_disconnected_callback(player_connection);
842 }
843 
EnqueueEvent(const NullaryFn & fn)844 void ServerNetworking::EnqueueEvent(const NullaryFn& fn)
845 { m_event_queue.push(fn); }
846