The Battle for Wesnoth  1.15.0-dev
server_base.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2018 by Sergey Popov <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License 2
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "server/server_base.hpp"
16 
17 #include "lexical_cast.hpp"
18 #include "log.hpp"
19 #include "utils/functional.hpp"
20 
21 static lg::log_domain log_server("server");
22 #define ERR_SERVER LOG_STREAM(err, log_server)
23 #define WRN_SERVER LOG_STREAM(warn, log_server)
24 #define LOG_SERVER LOG_STREAM(info, log_server)
25 #define DBG_SERVER LOG_STREAM(debug, log_server)
26 
27 static lg::log_domain log_config("config");
28 #define ERR_CONFIG LOG_STREAM(err, log_config)
29 #define WRN_CONFIG LOG_STREAM(warn, log_config)
30 
32 
33 server_base::server_base(unsigned short port, bool keep_alive) :
34  port_(port),
35  keep_alive_(keep_alive),
36  io_service_(),
37  acceptor_v6_(io_service_),
38  acceptor_v4_(io_service_),
39  #ifndef _WIN32
40  input_(io_service_),
41  sighup_(io_service_, SIGHUP),
42  #endif
43  sigs_(io_service_, SIGINT, SIGTERM)
44 {
45 }
46 
47 void server_base::setup_acceptor(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::ip::tcp::endpoint endpoint)
48 {
49  acceptor.open(endpoint.protocol());
50  acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
51  acceptor.set_option(boost::asio::ip::tcp::acceptor::keep_alive(keep_alive_));
52  if(endpoint.protocol() == boost::asio::ip::tcp::v6())
53  acceptor.set_option(boost::asio::ip::v6_only(true));
54  acceptor.bind(endpoint);
55  acceptor.listen();
56 }
57 
59 {
60  boost::asio::ip::tcp::endpoint endpoint_v6(boost::asio::ip::tcp::v6(), port_);
61  setup_acceptor(acceptor_v6_, endpoint_v6);
63 
64  boost::asio::ip::tcp::endpoint endpoint_v4(boost::asio::ip::tcp::v4(), port_);
65  setup_acceptor(acceptor_v4_, endpoint_v4);
67 
68  handshake_response_.connection_num = htonl(42);
69 
70 #ifndef _WIN32
71  sighup_.async_wait(
72  [=](const boost::system::error_code& error, int sig)
73  { this->handle_sighup(error, sig); });
74 #endif
75  sigs_.async_wait(std::bind(&server_base::handle_termination, this, _1, _2));
76 }
77 
78 void server_base::serve(boost::asio::ip::tcp::acceptor& acceptor)
79 {
80  socket_ptr socket = std::make_shared<boost::asio::ip::tcp::socket>(std::ref(io_service_));
81  acceptor.async_accept(*socket, [&acceptor, socket, this](const boost::system::error_code& error){
82  this->accept_connection(acceptor, error, socket);
83  });
84 }
85 
86 void server_base::accept_connection(boost::asio::ip::tcp::acceptor& acceptor, const boost::system::error_code& error, socket_ptr socket)
87 {
89  serve(acceptor);
90  if(error) {
91  ERR_SERVER << "Accept failed: " << error.message() << "\n";
92  return;
93  }
94 
95 #ifndef _WIN32
96  if(keep_alive_) {
97  int timeout = 30;
98 #ifdef __linux__
99  int cnt = 10;
100  int interval = 30;
101  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPIDLE, &timeout, sizeof(timeout));
102  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
103  setsockopt(socket->native_handle(), SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
104 #endif
105 #if defined(__APPLE__) && defined(__MACH__)
106  setsockopt(socket->native_handle(), IPPROTO_TCP, TCP_KEEPALIVE, &timeout, sizeof(timeout));
107 #endif
108  }
109 #endif
110 
111  DBG_SERVER << client_address(socket) << "\tnew connection tentatively accepted\n";
112  serverside_handshake(socket);
113 }
114 
116 {
117  boost::shared_array<char> handshake(new char[4]);
118  async_read(
119  *socket, boost::asio::buffer(handshake.get(), 4),
120  std::bind(&server_base::handle_handshake, this, _1, socket, handshake)
121  );
122 }
123 
124 void server_base::handle_handshake(const boost::system::error_code& error, socket_ptr socket, boost::shared_array<char> handshake)
125 {
126  if(check_error(error, socket))
127  return;
128 
129  if(memcmp(handshake.get(), "\0\0\0\0", 4) != 0) {
130  ERR_SERVER << client_address(socket) << "\tincorrect handshake\n";
131  return;
132  }
133  async_write(
134  *socket, boost::asio::buffer(handshake_response_.buf, 4),
135  [=](const boost::system::error_code& error, std::size_t)
136  {
137  if(!check_error(error, socket)) {
138  const std::string ip = client_address(socket);
139 
140  const std::string reason = is_ip_banned(ip);
141  if (!reason.empty()) {
142  LOG_SERVER << ip << "\trejected banned user. Reason: " << reason << "\n";
143  async_send_error(socket, "You are banned. Reason: " + reason);
144  return;
145  } else if (ip_exceeds_connection_limit(ip)) {
146  LOG_SERVER << ip << "\trejected ip due to excessive connections\n";
147  async_send_error(socket, "Too many connections from your IP.");
148  return;
149  } else {
150  DBG_SERVER << ip << "\tnew connection fully accepted\n";
151  this->handle_new_client(socket);
152  }
153  }
154  }
155  );
156 }
157 
158 #ifndef _WIN32
160  async_read_until(input_,
161  admin_cmd_, '\n',
162  [=](const boost::system::error_code& error, std::size_t bytes_transferred)
163  { this->handle_read_from_fifo(error, bytes_transferred); }
164  );
165 }
166 #endif
167 
168 void server_base::handle_termination(const boost::system::error_code& error, int signal_number)
169 {
170  assert(!error);
171 
172  std::string signame;
173  if(signal_number == SIGINT) signame = "SIGINT";
174  else if(signal_number == SIGTERM) signame = "SIGTERM";
175  else signame = lexical_cast<std::string>(signal_number);
176  LOG_SERVER << signame << " caught, exiting without cleanup immediately.\n";
177  exit(128 + signal_number);
178 }
179 
181  try {
182  io_service_.run();
183  LOG_SERVER << "Server has shut down because event loop is out of work\n";
184  } catch(const server_shutdown& e) {
185  LOG_SERVER << "Server has been shut down: " << e.what() << "\n";
186  }
187 }
188 
189 std::string client_address(const socket_ptr socket)
190 {
191  boost::system::error_code error;
192  std::string result = socket->remote_endpoint(error).address().to_string();
193  if(error)
194  return "<unknown address>";
195  else
196  return result;
197 }
198 
199 bool check_error(const boost::system::error_code& error, socket_ptr socket)
200 {
201  if(error) {
202  if(error == boost::asio::error::eof)
203  LOG_SERVER << client_address(socket) << "\tconnection closed\n";
204  else
205  ERR_SERVER << client_address(socket) << "\t" << error.message() << "\n";
206  return true;
207  }
208  return false;
209 }
210 
211 void async_send_error(socket_ptr socket, const std::string& msg, const char* error_code)
212 {
214  doc.root().add_child("error").set_attr_dup("message", msg.c_str());
215  if(*error_code != '\0') {
216  doc.child("error")->set_attr("error_code", error_code);
217  }
218 
219  async_send_doc(socket, doc);
220 }
221 
222 void async_send_warning(socket_ptr socket, const std::string& msg, const char* warning_code)
223 {
225  doc.root().add_child("warning").set_attr_dup("message", msg.c_str());
226  if(*warning_code != '\0') {
227  doc.child("warning")->set_attr("warning_code", warning_code);
228  }
229 
230  async_send_doc(socket, doc);
231 }
232 
233 void async_send_message(socket_ptr socket, const std::string& msg)
234 {
236  doc.root().add_child("message").set_attr_dup("message", msg.c_str());
237 
238  async_send_doc(socket, doc);
239 }
240 
241 // This is just here to get it to build without the deprecation_message function
242 #include "game_version.hpp"
243 #include "deprecation.hpp"
244 
245 std::string deprecated_message(const std::string&, DEP_LEVEL, const version_info&, const std::string&) {return "";}
boost::asio::ip::tcp::acceptor acceptor_v4_
Definition: server_base.hpp:47
node & add_child(const char *name)
Definition: simple_wml.cpp:464
virtual void handle_sighup(const boost::system::error_code &error, int signal_number)=0
void handle_handshake(const boost::system::error_code &error, socket_ptr socket, boost::shared_array< char > buf)
#define LOG_SERVER
Definition: server_base.cpp:24
bool check_error(const boost::system::error_code &error, socket_ptr socket)
Interfaces for manipulating version numbers of engine, add-ons, etc.
boost::asio::signal_set sighup_
Definition: server_base.hpp:73
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
New lexcical_cast header.
node & set_attr(const char *key, const char *value)
Definition: simple_wml.cpp:411
void handle_termination(const boost::system::error_code &error, int signal_number)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
To lexical_cast(From value)
Lexical cast converts one type to another.
void serverside_handshake(socket_ptr socket)
unsigned short port_
Definition: server_base.hpp:43
void async_send_warning(socket_ptr socket, const std::string &msg, const char *warning_code)
#define ERR_SERVER
Definition: server_base.cpp:22
void start_server()
Definition: server_base.cpp:58
node * child(const char *name)
Definition: simple_wml.hpp:260
bool keep_alive_
Definition: server_base.hpp:44
boost::asio::streambuf admin_cmd_
Definition: server_base.hpp:71
void serve(boost::asio::ip::tcp::acceptor &acceptor)
Definition: server_base.cpp:78
boost::asio::io_service io_service_
Definition: server_base.hpp:45
#define DBG_SERVER
Definition: server_base.cpp:25
boost::asio::ip::tcp::acceptor acceptor_v6_
Definition: server_base.hpp:46
virtual bool accepting_connections() const
Definition: server_base.hpp:62
const char * what() const noexcept
Definition: exceptions.hpp:37
virtual void handle_read_from_fifo(const boost::system::error_code &error, std::size_t bytes_transferred)=0
static lg::log_domain log_server("server")
void accept_connection(boost::asio::ip::tcp::acceptor &acceptor, const boost::system::error_code &error, socket_ptr socket)
Definition: server_base.cpp:86
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:427
server_base(unsigned short port, bool keep_alive)
Definition: server_base.cpp:33
static lg::log_domain log_config("config")
std::string client_address(const socket_ptr socket)
void read_from_fifo()
void setup_acceptor(boost::asio::ip::tcp::acceptor &acceptor, boost::asio::ip::tcp::endpoint endpoint)
Definition: server_base.cpp:47
boost::asio::posix::stream_descriptor input_
Definition: server_base.hpp:67
Represents version numbers.
std::string deprecated_message(const std::string &, DEP_LEVEL, const version_info &, const std::string &)
union server_base::@20 handshake_response_
void async_send_doc(socket_ptr socket, simple_wml::document &doc, Handler handler, ErrorHandler error_handler)
DEP_LEVEL
See https://wiki.wesnoth.org/CompatibilityStandards for more info.
Definition: deprecation.hpp:19
void async_send_error(socket_ptr socket, const std::string &msg, const char *error_code)
Standard logging facilities (interface).
std::shared_ptr< boost::asio::ip::tcp::socket > socket_ptr
Definition: server_base.hpp:28
void async_send_message(socket_ptr socket, const std::string &msg)
#define e
Base class for servers using Wesnoth&#39;s WML over TCP protocol.
boost::asio::signal_set sigs_
Definition: server_base.hpp:76