Line data Source code
1 : #ifndef THINGER_ASIO_LISTENER_HPP
2 : #define THINGER_ASIO_LISTENER_HPP
3 :
4 : #include <chrono>
5 : #include <cstddef>
6 : #include <cstdint>
7 : #include <functional>
8 : #include <memory>
9 : #include <string>
10 : #include <variant>
11 : #include <vector>
12 : #include <boost/asio/any_io_executor.hpp>
13 : #include <boost/system/error_code.hpp>
14 : #include "ssl/tls_session.hpp"
15 : #include "../util/types.hpp"
16 :
17 : namespace thinger::asio {
18 :
19 : class socket;
20 : class certificate_manager;
21 :
22 : namespace detail {
23 : // The internal switches of a listener (see detail/listener_access.hpp)
24 : struct listener_access;
25 : }
26 :
27 : // A TCP endpoint to listen on: a host name or address (resolved synchronously by open()) and a
28 : // port, 0 for one chosen by the system (see listener::local_port())
29 : struct tcp_endpoint {
30 : std::string host = "0.0.0.0";
31 : uint16_t port = 0;
32 : };
33 :
34 : // A Unix domain socket to listen on: a file there before is replaced, and the file is removed
35 : // when the listener stops
36 : struct unix_endpoint {
37 : std::string path;
38 : };
39 :
40 : // How a listener listens. Plain data: the listener copies it
41 : struct listener_options {
42 : std::variant<tcp_endpoint, unix_endpoint> endpoint;
43 : // The certificates of TLS: null for plain connections; one manager per listener, or shared
44 : // by several. The listener holds it until stop(), whatever open() returns: its connections
45 : // start with the default certificate open() found, and take the certificate and the client
46 : // certificate mode of their host name (SNI) from the manager. The application need not keep
47 : // a reference to it
48 : std::shared_ptr<certificate_manager> tls;
49 : // The client certificates asked for. A handshake that names a host (SNI) with a mode of its
50 : // own (see certificate_manager::set_client_verify()) gets that mode; every other handshake
51 : // gets this one. Ignored (logged) without tls
52 : client_verify_options client_verify;
53 : // How long a TLS client has to complete its handshake, from the connection accepted
54 : // (waiting for a slot included, see below): closed otherwise, without reaching the
55 : // handler. 0: no limit
56 15 : std::chrono::milliseconds handshake_timeout{60000};
57 : // TLS handshakes in progress at once per IO thread (per serve executor); a connection whose
58 : // thread is at the bound waits for a slot before its handshake starts, while accepting goes
59 : // on. 0: unbounded. With no handshake_timeout, connections waiting behind stalled
60 : // handshakes wait for as long as those do
61 : size_t max_pending_handshakes = 0;
62 : // Applied to the contexts of `tls`: listeners sharing a manager must agree (see open())
63 : session_cache_options session_cache;
64 : bool tcp_no_delay = true;
65 : // Attempts to bind before open() fails, every 5 s; -1: until it binds
66 : int max_listening_attempts = -1;
67 : // Remote addresses of TCP connections closed as they are accepted: a forbidden address is
68 : // always closed, and a non-empty allowed list closes every address not in it
69 : std::vector<std::string> allowed_remotes;
70 : std::vector<std::string> forbidden_remotes;
71 : };
72 :
73 : // The executor to accept on, or the next one to serve a connection on. In this release it must
74 : // run on an io_context (an io_context executor, or a strand of one)
75 : using executor_provider = std::function<boost::asio::any_io_executor()>;
76 :
77 : // Accepts connections on a TCP endpoint, with or without TLS, or on a Unix domain socket, and
78 : // hands each one to a handler: TLS connections once their handshake succeeded.
79 : //
80 : // open() binds and listens, start() accepts, stop() ends it: a listener is used once. It
81 : // accepts on the executor of `accept_on` and serves each connection on the next executor of
82 : // `serve_on`, where its handshake runs and its handler is called (a plain connection is handed
83 : // over on the executor it was accepted on).
84 : //
85 : // stop() may be called from any thread, the handler included, and more than once. Once it
86 : // returns, no handler starts anymore and none runs on another thread: called outside the
87 : // handler, it waits for the handlers running on other threads to return, so a handler must
88 : // never wait for a thread calling stop(). The destructor stops the listener.
89 : class listener {
90 : public:
91 : listener(listener_options options, executor_provider accept_on, executor_provider serve_on);
92 : ~listener();
93 :
94 : listener(const listener&) = delete;
95 : listener& operator=(const listener&) = delete;
96 : listener(listener&&) = delete;
97 : listener& operator=(listener&&) = delete;
98 :
99 : // Bind and listen. An error if it cannot: `invalid_argument` for options that cannot be
100 : // used (an executor not on an io_context, unreadable trusted CAs, a manager without a
101 : // context, a session cache that differs from the one of another listener of the same
102 : // manager), `already_open` if it was opened (or stopped) before, or the error resolving
103 : // the host or binding (after max_listening_attempts). Resolving is synchronous
104 : boost::system::error_code open();
105 : // Accept connections, handing each one to `on_connection` (see above). Nothing (logged) if
106 : // the listener is not open, or started already
107 : void start(sync_callback<void(std::shared_ptr<socket>)> on_connection);
108 : // Stop accepting: closes the listening socket and the connections whose handshake is
109 : // pending (see above)
110 : void stop();
111 :
112 : // The port listened on: 0 before open(), after stop(), and for Unix domain sockets
113 : uint16_t local_port() const;
114 : // Where it listens, once open: "http://0.0.0.0:8080", "https://[::1]:443", "unix:/run/x.sock"
115 : std::string url() const;
116 :
117 : private:
118 : friend struct detail::listener_access;
119 : struct state;
120 : std::shared_ptr<state> state_;
121 : };
122 :
123 : } // namespace thinger::asio
124 :
125 : #endif // THINGER_ASIO_LISTENER_HPP
|