Line data Source code
1 :
2 : #ifndef THINGER_ASIO_SOCKET_HPP
3 : #define THINGER_ASIO_SOCKET_HPP
4 :
5 : #include <mutex>
6 : #include <map>
7 : #include <memory>
8 : #include <optional>
9 : #include <string_view>
10 :
11 : #include <boost/asio.hpp>
12 : #include <boost/asio/buffer.hpp>
13 : #include <boost/system/error_code.hpp>
14 :
15 : #include "../../util/types.hpp"
16 : #include "../ssl/tls_session.hpp"
17 :
18 : namespace thinger::asio {
19 :
20 : // Byte stream socket: TCP, TLS, Unix, a WebSocket (see websocket.hpp), or a socket defined by
21 : // the application (e.g. a virtual stream multiplexed inside another connection), all used the
22 : // same way by the protocols and tunnels running on them (see socket_pipe.hpp).
23 : //
24 : // Like the asio objects they wrap, sockets are not thread-safe: use them (close() and cancel()
25 : // included) on a thread running their io_context (see get_io_context()), dispatching there
26 : // from any other thread. At most one read and one write may be pending at the same time
27 : // (a websocket queues concurrent writes).
28 : //
29 : // A socket defined by the application must implement read_some(), write(buffer, size),
30 : // close(), cancel() and is_open(); the other methods have defaults built on them (read(),
31 : // read_until() and the other writes) or answering as a socket that does not support them
32 : // (connect(), wait(), and the getters); `using socket::read; using socket::write;` keeps the
33 : // default overloads visible on the derived type. Its operations must keep these guarantees:
34 : // - read_some() waits until at least one byte is available, and returns it (never 0 bytes
35 : // without an error, unless max_size is 0); boost::asio::error::eof once the peer closed
36 : // the stream and everything it sent was read.
37 : // - write() returns once all the bytes are written (accepted by the socket, as a TCP socket
38 : // copies them to the kernel), or with an error.
39 : // - close() closes the socket right away, without waiting: is_open() is false afterwards, the
40 : // pending operations complete with an error (operation_aborted) and new ones fail. It may be
41 : // called several times, and from the destructor.
42 : // - cancel() makes the pending operations complete with operation_aborted; the socket stays
43 : // open.
44 : // - Operations complete on the io_context of the socket. Data arriving from another thread
45 : // (e.g. from the connection a virtual stream runs inside) is handed over by posting it to
46 : // that io_context.
47 : // - Pending operations should support per-operation cancellation (as asio operations and
48 : // timers do), used by the awaitable operators (||). If they do not, close() must still
49 : // complete them.
50 : class socket : private boost::asio::noncopyable {
51 :
52 : public:
53 : // constructors and destructors
54 : socket(const std::string &context, boost::asio::io_context &io_context);
55 : virtual ~socket();
56 :
57 : // socket control
58 : // Connect to host:port (with the TLS handshake if required); operation_not_supported
59 : // by default. `timeout` bounds the whole call, taken as a deadline when it starts: the
60 : // name resolution, the connect and, over TLS, the client handshake share it, each phase
61 : // getting the time left. Once it passes, the socket is closed and the call returns
62 : // boost::asio::error::timed_out (operation_aborted if the caller cancelled it). Other
63 : // errors are returned as they are (a handshake error with the socket closed). The bound of
64 : // the resolution is best-effort: asio cannot interrupt getaddrinfo, so a resolver that
65 : // hangs ends the call only once it returns (with timed_out if the deadline passed); a host
66 : // given as an address is not resolved
67 : virtual awaitable<boost::system::error_code> connect(
68 : const std::string &host,
69 : const std::string &port,
70 : std::chrono::seconds timeout);
71 : virtual void close() = 0;
72 : virtual void cancel() = 0;
73 : virtual bool requires_handshake() const;
74 : virtual awaitable<boost::system::error_code> handshake(const std::string &host = "");
75 :
76 : // read operations
77 : // Up to max_size bytes, at least one (see above)
78 : virtual awaitable<io_result> read_some(uint8_t buffer[], size_t max_size) = 0;
79 : // As read_some(), for a caller that ends this read alone: a cancellation through `slot` ends
80 : // it (operation_aborted), not whatever else waits on the socket, as cancel() would, and that
81 : // of the caller does not reach it, which is for the caller to forward. The default runs
82 : // read_some() on a coroutine of its own (which costs one per call); TCP, TLS and Unix bind
83 : // the slot to the read itself
84 : virtual awaitable<io_result> read_some_cancellable(uint8_t buffer[], size_t max_size,
85 : boost::asio::cancellation_slot slot);
86 : // Exactly `size` bytes (fewer only with an error)
87 : virtual awaitable<io_result> read(uint8_t buffer[], size_t size);
88 : // Exactly `size` bytes appended to `buffer` (fewer only with an error)
89 : virtual awaitable<io_result> read(boost::asio::streambuf &buffer, size_t size);
90 : // Read into `buffer` until it contains `delim`, as boost::asio::async_read_until: the
91 : // bytes up to the end of the delimiter (the buffer may hold more); not_found if the
92 : // buffer gets full before
93 : virtual awaitable<io_result> read_until(boost::asio::streambuf &buffer, std::string_view delim);
94 :
95 : // write operations: all the bytes, or an error
96 : virtual awaitable<io_result> write(const uint8_t buffer[], size_t size) = 0;
97 : virtual awaitable<io_result> write(std::string_view str);
98 : virtual awaitable<io_result> write(const std::vector<boost::asio::const_buffer> &buffers);
99 :
100 : // Write what the socket accepts right now, without waiting: the bytes written, with
101 : // would_block if none, or operation_not_supported if the socket cannot write without
102 : // waiting (then nothing is written). Not to be mixed with a pending write.
103 : virtual size_t write_now(const std::vector<boost::asio::const_buffer> &buffers, boost::system::error_code &ec);
104 :
105 : // As write(buffers), in slices of WRITE_SLICE_SIZE (a socket buffer), `progress` called after
106 : // each one: a write deadline it moves tells a slow peer that keeps reading from one that
107 : // stopped, as nginx's send_timeout does. `buffers` is left with what was not written
108 : static constexpr size_t WRITE_SLICE_SIZE = 64 * 1024;
109 : awaitable<io_result> write_slices(std::vector<boost::asio::const_buffer> &buffers,
110 : const sync_callback<void()> &progress);
111 : // Drop the first `size` bytes of `buffers` (and the empty buffers they leave first)
112 : static void consume(std::vector<boost::asio::const_buffer> &buffers, size_t size);
113 :
114 : // wait (operation_not_supported by default: then an HTTP server on the socket does not
115 : // watch for a client leaving while a response is pending)
116 : virtual awaitable<boost::system::error_code> wait(boost::asio::socket_base::wait_type type);
117 :
118 : // Once the socket is readable (see wait()): why, without consuming anything. No error if
119 : // the peer sent data (it stays available to the next read), eof if it closed its sending
120 : // side (a FIN; over TLS a close_notify, or a FIN without one), the error of the connection
121 : // otherwise (a reset). operation_not_supported by default, as wait(). It may wait for the
122 : // rest of a TLS record
123 : virtual awaitable<boost::system::error_code> peer_closed();
124 :
125 : // some getters to check the state
126 : virtual bool is_open() const = 0;
127 : // false by default
128 : virtual bool is_secure() const;
129 : // Bytes that can be read without waiting (0 by default)
130 : virtual size_t available() const;
131 : // Bytes written that the peer has not taken yet, as the system counts them (over TCP not
132 : // acknowledged, over a Unix socket not read); 0 by default (unknown). It only falls as the
133 : // peer takes them, so a stalled write tells a slow reader from one that stopped reading
134 : virtual size_t send_backlog() const;
135 : // Without waiting: whether a read would complete now, with data, the end of the stream or
136 : // an error (e.g. on an idle connection, which should have nothing to read, the peer
137 : // closing it). False by default (unknown)
138 : virtual bool readable_now() const;
139 : // Addresses of the connection (empty by default)
140 : virtual std::string get_remote_ip() const;
141 : virtual std::string get_local_port() const;
142 : virtual std::string get_remote_port() const;
143 : // TLS details of the connection once its handshake is done: none for a plain socket (the
144 : // default), or before the handshake or once closed. A socket running on another one (a
145 : // websocket) answers for it, so the caller never casts
146 : virtual std::optional<tls_session> tls() const;
147 : // Whether a request for the host name `host` (its Host, port removed) is misdirected on
148 : // this connection: its TLS handshake chose another client certificate mode or certificate
149 : // than `host` gets (see tls_handshake_record::misdirected()). False without TLS (the default)
150 0 : virtual bool misdirected(std::string_view /*host*/) const { return false; }
151 :
152 : // other methods
153 : boost::asio::io_context &get_io_context() const;
154 :
155 : protected:
156 : using native_handle_type = boost::asio::ip::tcp::socket::native_handle_type;
157 :
158 : // write_now() of a plain stream socket: a single non-blocking gathered send (the
159 : // buffers past the first 64 are left, as if the socket accepted no more)
160 : static size_t send_now(native_handle_type descriptor, const std::vector<boost::asio::const_buffer>& buffers,
161 : boost::system::error_code& ec);
162 :
163 : // readable_now() of a plain stream socket: polled with no timeout (true if the poll fails)
164 : static bool poll_readable(native_handle_type descriptor);
165 :
166 : // send_backlog() of a plain stream socket: SIOCOUTQ on Linux, SO_NWRITE on macOS (0 if
167 : // neither tells, or on an error)
168 : static size_t output_queue(native_handle_type descriptor);
169 :
170 : // peer_closed() of a plain stream socket: a non-blocking peek of one byte, which leaves it
171 : // in the socket. No error with data (or nothing to read yet), eof at the end of the stream,
172 : // else the error of the socket
173 : static boost::system::error_code peek_closed(native_handle_type descriptor);
174 :
175 : boost::asio::io_context &io_context_;
176 : static std::atomic<unsigned long> connections;
177 : };
178 :
179 : namespace detail {
180 :
181 : // async_read_some() that a cancellation through `slot` ends alone, completing as
182 : // use_nothrow_awaitable does. A handler of its own, not bind_cancellation_slot(slot,
183 : // use_nothrow_awaitable): an echo of 128 KiB takes 4% more cycles with the binder (the same
184 : // instructions), even with a slot not connected; with this one, connected as it is, none.
185 : // For TCP and Unix: over TLS the binder is not slower (see ssl_socket::read_some_cancellable())
186 : template <typename Handler>
187 : struct on_slot_handler {
188 : Handler handler;
189 : boost::asio::cancellation_slot slot;
190 :
191 : using cancellation_slot_type = boost::asio::cancellation_slot;
192 36707 : cancellation_slot_type get_cancellation_slot() const noexcept { return slot; }
193 :
194 : using executor_type = boost::asio::associated_executor_t<Handler>;
195 36707 : executor_type get_executor() const noexcept { return boost::asio::get_associated_executor(handler); }
196 :
197 36383 : void operator()(boost::system::error_code ec, size_t bytes) { std::move(handler)(io_result{ec, bytes}); }
198 : };
199 :
200 : template <typename Stream>
201 36707 : awaitable<io_result> read_some_on_slot(Stream& stream, uint8_t* buffer, size_t max_size,
202 : boost::asio::cancellation_slot slot) {
203 : return boost::asio::async_initiate<const boost::asio::use_awaitable_t<>, void(io_result)>(
204 73414 : [&stream, buffer, max_size, slot](auto handler) {
205 36707 : stream.async_read_some(boost::asio::buffer(buffer, max_size),
206 36707 : on_slot_handler<decltype(handler)>{std::move(handler), slot});
207 : },
208 73414 : boost::asio::use_awaitable);
209 : }
210 :
211 : }
212 :
213 : }
214 :
215 : #endif
|