Line data Source code
1 : #include <algorithm>
2 : #include <charconv>
3 : #include <iostream>
4 : #include <fcntl.h>
5 : #include "tcp_socket.hpp"
6 : #include "../with_timeout.hpp"
7 :
8 : namespace thinger::asio {
9 :
10 24883 : tcp_socket::tcp_socket(const std::string& context, boost::asio::io_context& io_context)
11 24883 : : socket(context, io_context), socket_(io_context) {
12 24883 : }
13 :
14 144 : tcp_socket::tcp_socket(const std::string& context, const std::shared_ptr<tcp_socket>& tcp_socket)
15 144 : : socket(context, tcp_socket->get_io_context()), socket_(std::move(tcp_socket->get_socket())) {
16 144 : }
17 :
18 10805 : tcp_socket::tcp_socket(const std::string& context, boost::asio::ip::tcp::socket&& sock)
19 10805 : : socket(context, static_cast<boost::asio::io_context&>(sock.get_executor().context())), socket_(std::move(sock)) {
20 10805 : }
21 :
22 35832 : tcp_socket::~tcp_socket() {
23 35832 : LOG_TRACE("releasing tcp connection");
24 35832 : close();
25 35832 : }
26 :
27 63417 : void tcp_socket::close() {
28 63417 : boost::system::error_code ec;
29 63417 : if (socket_.is_open()) {
30 24983 : socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
31 : }
32 63417 : socket_.close(ec);
33 63417 : LOG_TRACE("closing tcp socket result: {}", ec.message());
34 63417 : }
35 :
36 656 : bool tcp_socket::shutdown_send() {
37 656 : boost::system::error_code ec;
38 656 : socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
39 1312 : return !ec;
40 : }
41 :
42 5729 : void tcp_socket::cancel() {
43 5729 : socket_.cancel();
44 5722 : }
45 :
46 5897 : awaitable<boost::system::error_code> tcp_socket::connect(
47 : const std::string& host,
48 : const std::string& port,
49 : std::chrono::seconds timeout)
50 : {
51 : // One deadline for the whole call: each phase (resolve, connect, handshake) gets the time
52 : // left until it, so a peer cannot stretch the call by answering just in time to each
53 : const auto deadline = std::chrono::steady_clock::now() + timeout;
54 7913 : auto time_left = [&deadline] {
55 7913 : return std::max(deadline - std::chrono::steady_clock::now(), std::chrono::steady_clock::duration::zero());
56 : };
57 : // A phase did not end in time: the socket is closed, and the error is timed_out, or
58 : // operation_aborted when the caller cancelled the call (the timer may then win the race)
59 21 : auto expired = [this](const boost::asio::cancellation_state& state) {
60 21 : close();
61 21 : return state.cancelled() != boost::asio::cancellation_type::none
62 21 : ? boost::system::error_code{boost::asio::error::operation_aborted}
63 21 : : boost::system::error_code{boost::asio::error::timed_out};
64 : };
65 :
66 : close();
67 :
68 : // Resolve the host, unless it is already an address with a numeric port. asio runs
69 : // getaddrinfo on a thread of its own and cannot interrupt it: a resolve that hangs ends
70 : // the call only once getaddrinfo returns (its own timeouts), with timed_out
71 : using results_type = boost::asio::ip::tcp::resolver::results_type;
72 : results_type endpoints;
73 : boost::system::error_code address_ec;
74 : auto address = boost::asio::ip::make_address(host, address_ec);
75 : uint16_t port_number = 0;
76 : auto [port_end, port_ec] = std::from_chars(port.data(), port.data() + port.size(), port_number);
77 : if (!address_ec && port_ec == std::errc{} && port_end == port.data() + port.size()) {
78 : endpoints = results_type::create(boost::asio::ip::tcp::endpoint(address, port_number), host, port);
79 : } else {
80 : boost::asio::ip::tcp::resolver resolver(io_context_);
81 : auto resolved = co_await with_timeout(resolver.async_resolve(host, port, use_nothrow_awaitable), time_left());
82 : if (!resolved) co_return expired(co_await boost::asio::this_coro::cancellation_state);
83 : auto [resolve_ec, results] = std::move(*resolved);
84 : if (resolve_ec) co_return resolve_ec;
85 : endpoints = std::move(results);
86 : }
87 :
88 : // Connect: a connect not done in time is cancelled
89 : auto connected = co_await with_timeout(
90 : boost::asio::async_connect(socket_, endpoints, use_nothrow_awaitable), time_left());
91 : if (!connected) co_return expired(co_await boost::asio::this_coro::cancellation_state);
92 : auto connect_ec = std::get<0>(*connected);
93 : if (connect_ec) {
94 : co_return connect_ec;
95 : }
96 : // Not inherited by the programs the process runs (fork and exec): asio opens sockets
97 : // without SOCK_CLOEXEC
98 : ::fcntl(socket_.native_handle(), F_SETFD, FD_CLOEXEC);
99 :
100 : // Run the client handshake if required (for SSL sockets), with the time left: a peer that
101 : // accepts and stays silent, or trickles its answer, must not hold the call
102 : if (requires_handshake()) {
103 : auto handshaken = co_await with_timeout(handshake(host), time_left());
104 : if (!handshaken) co_return expired(co_await boost::asio::this_coro::cancellation_state);
105 : if (*handshaken) {
106 : close();
107 : co_return *handshaken;
108 : }
109 : }
110 :
111 : co_return boost::system::error_code{};
112 11794 : }
113 :
114 460126 : boost::asio::ip::tcp::socket& tcp_socket::get_socket() {
115 460126 : return socket_;
116 : }
117 :
118 23386 : std::string tcp_socket::get_remote_ip() const {
119 23386 : boost::system::error_code ec;
120 23386 : auto remote_ep = socket_.remote_endpoint(ec);
121 23386 : if (!ec) {
122 20326 : return remote_ep.address().to_string();
123 : }
124 6120 : return "0.0.0.0";
125 : }
126 :
127 20138 : std::string tcp_socket::get_local_port() const {
128 20138 : boost::system::error_code ec;
129 20138 : auto local_ep = socket_.local_endpoint(ec);
130 20138 : if (!ec) {
131 20138 : return std::to_string(local_ep.port());
132 : }
133 0 : return "0";
134 : }
135 :
136 1143 : std::string tcp_socket::get_remote_port() const {
137 1143 : boost::system::error_code ec;
138 1143 : auto remote_ep = socket_.remote_endpoint(ec);
139 1143 : if (!ec) {
140 1143 : return std::to_string(remote_ep.port());
141 : }
142 0 : return "0";
143 : }
144 :
145 169319 : awaitable<io_result> tcp_socket::read_some(uint8_t* buffer, size_t max_size) {
146 : co_return co_await socket_.async_read_some(
147 : boost::asio::buffer(buffer, max_size),
148 : use_nothrow_awaitable);
149 338638 : }
150 :
151 31432 : awaitable<io_result> tcp_socket::read_some_cancellable(uint8_t* buffer, size_t max_size,
152 : boost::asio::cancellation_slot slot) {
153 : co_return co_await detail::read_some_on_slot(socket_, buffer, max_size, slot);
154 62864 : }
155 :
156 606 : awaitable<io_result> tcp_socket::read(uint8_t* buffer, size_t size) {
157 : co_return co_await boost::asio::async_read(
158 : socket_,
159 : boost::asio::buffer(buffer, size),
160 : boost::asio::transfer_exactly(size),
161 : use_nothrow_awaitable);
162 1212 : }
163 :
164 3 : awaitable<io_result> tcp_socket::read(boost::asio::streambuf& buffer, size_t size) {
165 : co_return co_await boost::asio::async_read(
166 : socket_,
167 : buffer,
168 : boost::asio::transfer_exactly(size),
169 : use_nothrow_awaitable);
170 6 : }
171 :
172 3 : awaitable<io_result> tcp_socket::read_until(boost::asio::streambuf& buffer, std::string_view delim) {
173 : co_return co_await boost::asio::async_read_until(
174 : socket_,
175 : buffer,
176 : std::string(delim),
177 : use_nothrow_awaitable);
178 6 : }
179 :
180 54001 : awaitable<io_result> tcp_socket::write(const uint8_t* buffer, size_t size) {
181 : co_return co_await boost::asio::async_write(
182 : socket_,
183 : boost::asio::buffer(buffer, size),
184 : use_nothrow_awaitable);
185 108002 : }
186 :
187 19702 : awaitable<io_result> tcp_socket::write(std::string_view str) {
188 : co_return co_await boost::asio::async_write(
189 : socket_,
190 : boost::asio::buffer(str.data(), str.size()),
191 : use_nothrow_awaitable);
192 39404 : }
193 :
194 68231 : awaitable<io_result> tcp_socket::write(const std::vector<boost::asio::const_buffer>& buffers) {
195 : co_return co_await boost::asio::async_write(
196 : socket_,
197 : buffers,
198 : use_nothrow_awaitable);
199 136462 : }
200 :
201 20309 : size_t tcp_socket::write_now(const std::vector<boost::asio::const_buffer>& buffers, boost::system::error_code& ec) {
202 20309 : return send_now(socket_.native_handle(), buffers, ec);
203 : }
204 :
205 1365 : awaitable<boost::system::error_code> tcp_socket::wait(boost::asio::socket_base::wait_type type) {
206 : auto [ec] = co_await socket_.async_wait(type, use_nothrow_awaitable);
207 : co_return ec;
208 2730 : }
209 :
210 219 : awaitable<boost::system::error_code> tcp_socket::peer_closed() {
211 : co_return peek_closed(socket_.native_handle());
212 438 : }
213 :
214 24038 : bool tcp_socket::enable_tcp_no_delay() {
215 24038 : boost::system::error_code ec;
216 24038 : socket_.set_option(boost::asio::ip::tcp::no_delay(true), ec);
217 24038 : if (ec) LOG_DEBUG("cannot enable tcp_no_delay: {}", ec.message());
218 48076 : return !ec;
219 : }
220 :
221 5 : bool tcp_socket::disable_tcp_no_delay() {
222 5 : boost::system::error_code ec;
223 5 : socket_.set_option(boost::asio::ip::tcp::no_delay(false), ec);
224 5 : if (ec) LOG_DEBUG("cannot disable tcp_no_delay: {}", ec.message());
225 10 : return !ec;
226 : }
227 :
228 2600394 : bool tcp_socket::is_open() const {
229 2600394 : return socket_.is_open();
230 : }
231 :
232 21556 : bool tcp_socket::is_secure() const {
233 21556 : return false;
234 : }
235 :
236 3261 : bool tcp_socket::readable_now() const {
237 3261 : return socket_.is_open() && poll_readable(const_cast<boost::asio::ip::tcp::socket&>(socket_).native_handle());
238 : }
239 :
240 440 : size_t tcp_socket::send_backlog() const {
241 440 : return socket_.is_open() ? output_queue(const_cast<boost::asio::ip::tcp::socket&>(socket_).native_handle()) : 0;
242 : }
243 :
244 1973 : size_t tcp_socket::available() const {
245 1973 : boost::system::error_code ec;
246 1973 : auto size = socket_.available(ec);
247 1973 : if (ec) {
248 4 : LOG_ERROR("error while getting socket available bytes ({}): {}", size, ec.message());
249 : }
250 1973 : return size;
251 : }
252 :
253 : }
|