The Battle for Wesnoth  1.17.0-dev
network_asio.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2011 - 2021
3  by Sergey Popov <loonycyborg@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define BOOST_ASIO_NO_DEPRECATED
17 
18 #include "network_asio.hpp"
19 
20 #include "log.hpp"
21 #include "serialization/parser.hpp"
22 #include "tls_root_store.hpp"
23 
24 #include <boost/asio/connect.hpp>
25 #include <boost/asio/read.hpp>
26 #include <boost/asio/write.hpp>
27 
28 #include <algorithm>
29 #include <cstdint>
30 #include <deque>
31 #include <functional>
32 
33 static lg::log_domain log_network("network");
34 #define DBG_NW LOG_STREAM(debug, log_network)
35 #define LOG_NW LOG_STREAM(info, log_network)
36 #define WRN_NW LOG_STREAM(warn, log_network)
37 #define ERR_NW LOG_STREAM(err, log_network)
38 
39 namespace
40 {
41 std::deque<boost::asio::const_buffer> split_buffer(boost::asio::streambuf::const_buffers_type source_buffer)
42 {
43  const unsigned int chunk_size = 4096;
44 
45  std::deque<boost::asio::const_buffer> buffers;
46  unsigned int remaining_size = boost::asio::buffer_size(source_buffer);
47 
48  const uint8_t* data = static_cast<const uint8_t*>(source_buffer.data());
49 
50  while(remaining_size > 0u) {
51  unsigned int size = std::min(remaining_size, chunk_size);
52  buffers.emplace_back(data, size);
53  data += size;
54  remaining_size -= size;
55  }
56 
57  return buffers;
58 }
59 }
60 
61 namespace network_asio
62 {
63 using boost::system::system_error;
64 
65 connection::connection(const std::string& host, const std::string& service)
66  : io_context_()
67  , host_(host)
68  , service_(service)
69  , resolver_(io_context_)
70  , use_tls_(true)
71  , socket_(raw_socket(new raw_socket::element_type{io_context_}))
72  , done_(false)
73  , write_buf_()
74  , read_buf_()
76  , payload_size_(0)
77  , bytes_to_write_(0)
78  , bytes_written_(0)
79  , bytes_to_read_(0)
80  , bytes_read_(0)
81 {
82  boost::system::error_code ec;
83  auto result = resolver_.resolve(host, service, boost::asio::ip::resolver_query_base::numeric_host, ec);
84  if(!ec) { // if numeric resolve succeeds then we got raw ip address so TLS host name validation would never pass
85  use_tls_ = false;
86  boost::asio::post(io_context_, [this, ec, result](){ handle_resolve(ec, { result } ); } );
87  } else {
88  resolver_.async_resolve(host, service,
89  std::bind(&connection::handle_resolve, this, std::placeholders::_1, std::placeholders::_2));
90  }
91 
92  LOG_NW << "Resolving hostname: " << host << '\n';
93 }
94 
96 {
97  if(auto socket = utils::get_if<tls_socket>(&socket_)) {
98  boost::system::error_code ec;
99  // this sends close_notify for secure connection shutdown
100  (*socket)->async_shutdown([](const boost::system::error_code&) {} );
101  const char buffer[] = "";
102  // this write is needed to trigger immediate close instead of waiting for other side's close_notify
103  boost::asio::write(**socket, boost::asio::buffer(buffer, 0), ec);
104  }
105 }
106 
107 void connection::handle_resolve(const boost::system::error_code& ec, results_type results)
108 {
109  if(ec) {
110  throw system_error(ec);
111  }
112 
113  boost::asio::async_connect(*utils::get<raw_socket>(socket_), results,
114  std::bind(&connection::handle_connect, this, std::placeholders::_1, std::placeholders::_2));
115 }
116 
117 void connection::handle_connect(const boost::system::error_code& ec, endpoint endpoint)
118 {
119  if(ec) {
120  ERR_NW << "Tried all IPs. Giving up" << std::endl;
121  throw system_error(ec);
122  } else {
123  LOG_NW << "Connected to " << endpoint.address() << '\n';
124 
125  if(endpoint.address().is_loopback()) {
126  use_tls_ = false;
127  }
128  handshake();
129  }
130 }
131 
133 {
134  static const uint32_t handshake = 0;
135  static const uint32_t tls_handshake = htonl(uint32_t(1));
136 
137  boost::asio::async_write(
138  *utils::get<raw_socket>(socket_),
139  boost::asio::buffer(use_tls_ ? reinterpret_cast<const char*>(&tls_handshake) : reinterpret_cast<const char*>(&handshake), 4),
140  std::bind(&connection::handle_write, this, std::placeholders::_1, std::placeholders::_2)
141  );
142 
143  boost::asio::async_read(*utils::get<raw_socket>(socket_), boost::asio::buffer(reinterpret_cast<std::byte*>(&handshake_response_), 4),
144  std::bind(&connection::handle_handshake, this, std::placeholders::_1));
145 }
146 
147 void connection::handle_handshake(const boost::system::error_code& ec)
148 {
149  if(ec) {
150  if(ec == boost::asio::error::eof && use_tls_) {
151  // immediate disconnect likely means old server not supporting TLS handshake code
153  return;
154  }
155 
156  throw system_error(ec);
157  }
158 
159  if(use_tls_) {
160  if(handshake_response_ == 0xFFFFFFFFU) {
161  use_tls_ = false;
162  handle_handshake(ec);
163  return;
164  }
165 
166  if(handshake_response_ == 0x00000000) {
168  raw_socket s { std::move(utils::get<raw_socket>(socket_)) };
169  tls_socket ts { new tls_socket::element_type { std::move(*s), tls_context_ } };
170  socket_ = std::move(ts);
171 
172  auto& socket { *utils::get<tls_socket>(socket_) };
173 
174  socket.set_verify_mode(
175  boost::asio::ssl::verify_peer |
176  boost::asio::ssl::verify_fail_if_no_peer_cert
177  );
178 
179 #if BOOST_VERSION >= 107300
180  socket.set_verify_callback(boost::asio::ssl::host_name_verification(host_));
181 #else
182  socket.set_verify_callback(boost::asio::ssl::rfc2818_verification(host_));
183 #endif
184 
185  socket.async_handshake(boost::asio::ssl::stream_base::client, [this](const boost::system::error_code& ec) {
186  if(ec) {
187  throw system_error(ec);
188  }
189 
190  done_ = true;
191  });
192  return;
193  }
194 
196  } else {
197  done_ = true;
198  }
199 }
200 
202 {
203  assert(use_tls_ == true);
204  use_tls_ = false;
205 
206  boost::asio::ip::tcp::endpoint endpoint { utils::get<raw_socket>(socket_)->remote_endpoint() };
207  utils::get<raw_socket>(socket_)->close();
208 
209  utils::get<raw_socket>(socket_)->async_connect(endpoint,
210  std::bind(&connection::handle_connect, this, std::placeholders::_1, endpoint));
211 }
212 
213 void connection::transfer(const config& request, config& response)
214 {
215  io_context_.restart();
216  done_ = false;
217 
218  write_buf_.reset(new boost::asio::streambuf);
219  read_buf_.reset(new boost::asio::streambuf);
220  std::ostream os(write_buf_.get());
221  write_gz(os, request);
222 
223  bytes_to_write_ = write_buf_->size() + 4;
224  bytes_written_ = 0;
225  payload_size_ = htonl(bytes_to_write_ - 4);
226 
227  auto bufs = split_buffer(write_buf_->data());
228  bufs.push_front(boost::asio::buffer(reinterpret_cast<const char*>(&payload_size_), 4));
229 
230  utils::visit([this, &bufs, &response](auto&& socket) {
231  boost::asio::async_write(*socket, bufs,
232  std::bind(&connection::is_write_complete, this, std::placeholders::_1, std::placeholders::_2),
233  std::bind(&connection::handle_write, this, std::placeholders::_1, std::placeholders::_2));
234 
235  boost::asio::async_read(*socket, *read_buf_,
236  std::bind(&connection::is_read_complete, this, std::placeholders::_1, std::placeholders::_2),
237  std::bind(&connection::handle_read, this, std::placeholders::_1, std::placeholders::_2, std::ref(response)));
238  }, socket_);
239 }
240 
242 {
243  utils::visit([](auto&& socket) {
244  if(socket->lowest_layer().is_open()) {
245  boost::system::error_code ec;
246 
247 #ifdef _MSC_VER
248 // Silence warning about boost::asio::basic_socket<Protocol>::cancel always
249 // returning an error on XP, which we don't support anymore.
250 #pragma warning(push)
251 #pragma warning(disable:4996)
252 #endif
253  socket->lowest_layer().cancel(ec);
254 #ifdef _MSC_VER
255 #pragma warning(pop)
256 #endif
257 
258  if(ec) {
259  WRN_NW << "Failed to cancel network operations: " << ec.message() << std::endl;
260  }
261  }
262  }, socket_);
263  bytes_to_write_ = 0;
264  bytes_written_ = 0;
265  bytes_to_read_ = 0;
266  bytes_read_ = 0;
267 }
268 
269 std::size_t connection::is_write_complete(const boost::system::error_code& ec, std::size_t bytes_transferred)
270 {
271  if(ec) {
272  throw system_error(ec);
273  }
274 
275  bytes_written_ = bytes_transferred;
276  return bytes_to_write_ - bytes_transferred;
277 }
278 
279 void connection::handle_write(const boost::system::error_code& ec, std::size_t bytes_transferred)
280 {
281  DBG_NW << "Written " << bytes_transferred << " bytes.\n";
282  if(write_buf_)
283  write_buf_->consume(bytes_transferred);
284 
285  if(ec) {
286  throw system_error(ec);
287  }
288 }
289 
290 std::size_t connection::is_read_complete(const boost::system::error_code& ec, std::size_t bytes_transferred)
291 {
292  if(ec) {
293  throw system_error(ec);
294  }
295 
296  bytes_read_ = bytes_transferred;
297  if(bytes_transferred < 4) {
298  return 4;
299  }
300 
301  if(!bytes_to_read_) {
302  std::istream is(read_buf_.get());
303  uint32_t data_size;
304 
305  is.read(reinterpret_cast<char*>(&data_size), 4);
306  bytes_to_read_ = ntohl(data_size) + 4;
307 
308  // Close immediately if we receive an invalid length
309  if(bytes_to_read_ < 4) {
310  bytes_to_read_ = bytes_transferred;
311  }
312  }
313 
314  return bytes_to_read_ - bytes_transferred;
315 }
316 
317 void connection::handle_read(const boost::system::error_code& ec, std::size_t bytes_transferred, config& response)
318 {
319  DBG_NW << "Read " << bytes_transferred << " bytes.\n";
320 
321  bytes_to_read_ = 0;
322  bytes_to_write_ = 0;
323  done_ = true;
324 
325  if(ec && ec != boost::asio::error::eof) {
326  throw system_error(ec);
327  }
328 
329  std::istream is(read_buf_.get());
330  read_gz(response, is);
331 }
332 }
void handle_read(const boost::system::error_code &ec, std::size_t bytes_transferred, config &response)
connection(const std::string &host, const std::string &service)
Constructor.
void load_tls_root_certs(boost::asio::ssl::context &ctx)
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:683
#define ERR_NW
std::size_t is_write_complete(const boost::system::error_code &error, std::size_t bytes_transferred)
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
boost::asio::io_context io_context_
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
void handle_resolve(const boost::system::error_code &ec, results_type results)
void write_gz(std::ostream &out, const configr_of &cfg)
Definition: parser.cpp:783
#define WRN_NW
void handle_write(const boost::system::error_code &ec, std::size_t bytes_transferred)
std::size_t is_read_complete(const boost::system::error_code &error, std::size_t bytes_transferred)
const boost::asio::ip::tcp::endpoint & endpoint
static lg::log_domain log_network("network")
void transfer(const config &request, config &response)
boost::asio::ssl::context tls_context_
static map_location::DIRECTION s
#define LOG_NW
resolver::results_type results_type
std::unique_ptr< boost::asio::streambuf > read_buf_
std::unique_ptr< boost::asio::streambuf > write_buf_
Standard logging facilities (interface).
std::unique_ptr< boost::asio::ssl::stream< raw_socket::element_type > > tls_socket
std::unique_ptr< boost::asio::ip::tcp::socket > raw_socket
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
void handle_handshake(const boost::system::error_code &ec)
#define DBG_NW
void handle_connect(const boost::system::error_code &ec, endpoint endpoint)