Currently CConnman
is a mixture of:
- low level socket handling, e.g. send, recv, poll, bind, listen, connect, and
- higher level logic that is specific to the Bitcoin P2P protocol, e.g. V1/V2 transport, choosing which address to connect to, if we manage to connect mark the address good in
AddrMan
, maintaining the number of inbound and outbound connections, banning of peers, interacting withPeerManager
.
This PR splits the socket handling into a new class which makes the code more modular and reusable. Having more modular and reusable code is a good thing on its own, even if the code is not reused. Stratum V2 and libevent-less RPC/HTTP server could benefit from this, but it makes sense on its own, even without those projects.
The socket operations are driven by the new class SockMan
which informs the higher level via provided methods when e.g. new data arrives on the socket or a new connection is accepted. For this, SockMan
provides some non-virtual methods to start it rolling and then it calls pure virtual methods which are implemented by the higher level (e.g. CConnman
) on certain events, for example “got this new data on this node’s socket”.
The interface of SockMan
is:
0/**
1 * A socket manager class which handles socket operations.
2 * To use this class, inherit from it and implement the pure virtual methods.
3 * Handled operations:
4 * - binding and listening on sockets
5 * - starting of necessary threads to process socket operations
6 * - accepting incoming connections
7 * - making outbound connections
8 * - closing connections
9 * - waiting for IO readiness on sockets and doing send/recv accordingly
10 */
11class SockMan
12{
13public:
14
15 //
16 // Non-virtual functions, to be reused by children classes.
17 //
18
19 /**
20 * Bind to a new address:port, start listening and add the listen socket to `m_listen`.
21 * Should be called before `StartSocketsThreads()`.
22 * [@param](/bitcoin-bitcoin/contributor/param/)[in] to Where to bind.
23 * [@param](/bitcoin-bitcoin/contributor/param/)[out] err_msg Error string if an error occurs.
24 * [@retval](/bitcoin-bitcoin/contributor/retval/) true Success.
25 * [@retval](/bitcoin-bitcoin/contributor/retval/) false Failure, `err_msg` will be set.
26 */
27 bool BindAndStartListening(const CService& to, bilingual_str& err_msg);
28
29 /**
30 * Start the necessary threads for sockets IO.
31 */
32 void StartSocketsThreads(const Options& options);
33
34 /**
35 * Join (wait for) the threads started by `StartSocketsThreads()` to exit.
36 */
37 void JoinSocketsThreads();
38
39 /**
40 * Make an outbound connection, save the socket internally and return a newly generated connection id.
41 * [@param](/bitcoin-bitcoin/contributor/param/)[in] to The address to connect to, either as CService or a host as string and port as
42 * an integer, if the later is used, then `proxy` must be valid.
43 * [@param](/bitcoin-bitcoin/contributor/param/)[in] is_important If true, then log failures with higher severity.
44 * [@param](/bitcoin-bitcoin/contributor/param/)[in] proxy Proxy to connect through, if set.
45 * [@param](/bitcoin-bitcoin/contributor/param/)[out] proxy_failed If `proxy` is valid and the connection failed because of the
46 * proxy, then it will be set to true.
47 * [@param](/bitcoin-bitcoin/contributor/param/)[out] me If the connection was successful then this is set to the address on the
48 * local side of the socket.
49 * [@return](/bitcoin-bitcoin/contributor/return/) Newly generated id, or std::nullopt if the operation fails.
50 */
51 std::optional<SockMan::Id> ConnectAndMakeId(const std::variant<CService, StringHostIntPort>& to,
52 bool is_important,
53 std::optional<Proxy> proxy,
54 bool& proxy_failed,
55 CService& me)
56 EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex, !m_unused_i2p_sessions_mutex);
57
58 /**
59 * Destroy a given connection by closing its socket and release resources occupied by it.
60 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Connection to destroy.
61 * [@return](/bitcoin-bitcoin/contributor/return/) Whether the connection existed and its socket was closed by this call.
62 */
63 bool CloseConnection(Id id)
64 EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex);
65
66 /**
67 * Try to send some data over the given connection.
68 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Identifier of the connection.
69 * [@param](/bitcoin-bitcoin/contributor/param/)[in] data The data to send, it might happen that only a prefix of this is sent.
70 * [@param](/bitcoin-bitcoin/contributor/param/)[in] will_send_more Used as an optimization if the caller knows that they will
71 * be sending more data soon after this call.
72 * [@param](/bitcoin-bitcoin/contributor/param/)[out] errmsg If <0 is returned then this will contain a human readable message
73 * explaining the error.
74 * [@retval](/bitcoin-bitcoin/contributor/retval/) >=0 The number of bytes actually sent.
75 * [@retval](/bitcoin-bitcoin/contributor/retval/) <0 A permanent error has occurred.
76 */
77 ssize_t SendBytes(Id id,
78 std::span<const unsigned char> data,
79 bool will_send_more,
80 std::string& errmsg) const
81 EXCLUSIVE_LOCKS_REQUIRED(!m_connected_mutex);
82
83 /**
84 * Stop listening by closing all listening sockets.
85 */
86 void StopListening();
87
88 //
89 // Pure virtual functions must be implemented by children classes.
90 //
91
92 /**
93 * Be notified when a new connection has been accepted.
94 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Id of the newly accepted connection.
95 * [@param](/bitcoin-bitcoin/contributor/param/)[in] me The address and port at our side of the connection.
96 * [@param](/bitcoin-bitcoin/contributor/param/)[in] them The address and port at the peer's side of the connection.
97 * [@retval](/bitcoin-bitcoin/contributor/retval/) true The new connection was accepted at the higher level.
98 * [@retval](/bitcoin-bitcoin/contributor/retval/) false The connection was refused at the higher level, so the
99 * associated socket and id should be discarded by `SockMan`.
100 */
101 virtual bool EventNewConnectionAccepted(Id id,
102 const CService& me,
103 const CService& them) = 0;
104
105 /**
106 * Called when the socket is ready to send data and `ShouldTryToSend()` has
107 * returned true. This is where the higher level code serializes its messages
108 * and calls `SockMan::SendBytes()`.
109 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Id of the connection whose socket is ready to send.
110 * [@param](/bitcoin-bitcoin/contributor/param/)[out] cancel_recv Should always be set upon return and if it is true,
111 * then the next attempt to receive data from that connection will be omitted.
112 */
113 virtual void EventReadyToSend(Id id, bool& cancel_recv) = 0;
114
115 /**
116 * Called when new data has been received.
117 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Connection for which the data arrived.
118 * [@param](/bitcoin-bitcoin/contributor/param/)[in] data Received data.
119 */
120 virtual void EventGotData(Id id, std::span<const uint8_t> data) = 0;
121
122 /**
123 * Called when the remote peer has sent an EOF on the socket. This is a graceful
124 * close of their writing side, we can still send and they will receive, if it
125 * makes sense at the application level.
126 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Connection whose socket got EOF.
127 */
128 virtual void EventGotEOF(Id id) = 0;
129
130 /**
131 * Called when we get an irrecoverable error trying to read from a socket.
132 * [@param](/bitcoin-bitcoin/contributor/param/)[in] id Connection whose socket got an error.
133 * [@param](/bitcoin-bitcoin/contributor/param/)[in] errmsg Message describing the error.
134 */
135 virtual void EventGotPermanentReadError(Id id, const std::string& errmsg) = 0;
136};
Resolves: #30694
Review hint: this PR moves some code around, so reviewers may find this helpful: git show --color-moved --color-moved-ws=allow-indentation-change
.