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 public 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] errmsg Error string if an error occurs.
24 * [@retval](/bitcoin-bitcoin/contributor/retval/) true Success.
25 * [@retval](/bitcoin-bitcoin/contributor/retval/) false Failure, `strError` will be set.
26 */
27 bool BindAndStartListening(const CService& to, bilingual_str& errmsg);
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 node 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 `proxy.IsValid()` is true.
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 node id, or std::nullopt if the operation fails.
50 */
51 std::optional<NodeId> ConnectAndMakeNodeId(const std::variant<CService, StringHostIntPort>& to,
52 bool is_important,
53 const Proxy& proxy,
54 bool& proxy_failed,
55 CService& me);
56
57 /**
58 * Disconnect a given peer by closing its socket and release resources occupied by it.
59 * [@return](/bitcoin-bitcoin/contributor/return/) Whether the peer existed and its socket was closed by this call.
60 */
61 bool CloseConnection(NodeId node_id);
62
63 /**
64 * Try to send some data to the given node.
65 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Identifier of the node to send to.
66 * [@param](/bitcoin-bitcoin/contributor/param/)[in] data The data to send, it might happen that only a prefix of this is sent.
67 * [@param](/bitcoin-bitcoin/contributor/param/)[in] will_send_more Used as an optimization if the caller knows that they will
68 * be sending more data soon after this call.
69 * [@param](/bitcoin-bitcoin/contributor/param/)[out] errmsg If <0 is returned then this will contain a human readable message
70 * explaining the error.
71 * [@retval](/bitcoin-bitcoin/contributor/retval/) >=0 The number of bytes actually sent.
72 * [@retval](/bitcoin-bitcoin/contributor/retval/) <0 A permanent error has occurred.
73 */
74 ssize_t SendBytes(NodeId node_id,
75 Span<const unsigned char> data,
76 bool will_send_more,
77 std::string& errmsg) const;
78
79 /**
80 * Close all sockets.
81 */
82 void CloseSockets();
83
84 //
85 // Pure virtual functions must be implemented by children classes.
86 //
87
88 /**
89 * Be notified when a new connection has been accepted.
90 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Id of the newly accepted connection.
91 * [@param](/bitcoin-bitcoin/contributor/param/)[in] me The address and port at our side of the connection.
92 * [@param](/bitcoin-bitcoin/contributor/param/)[in] them The address and port at the peer's side of the connection.
93 * [@retval](/bitcoin-bitcoin/contributor/retval/) true The new connection was accepted at the higher level.
94 * [@retval](/bitcoin-bitcoin/contributor/retval/) false The connection was refused at the higher level, so the
95 * associated socket and node_id should be discarded by `SockMan`.
96 */
97 virtual bool EventNewConnectionAccepted(NodeId node_id,
98 const CService& me,
99 const CService& them) = 0;
100
101 /**
102 * Called when the socket is ready to send data and `ShouldTryToSend()` has
103 * returned true. This is where the higher level code serializes its messages
104 * and calls `SockMan::SendBytes()`.
105 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Id of the node whose socket is ready to send.
106 * [@param](/bitcoin-bitcoin/contributor/param/)[out] cancel_recv Should always be set upon return and if it is true,
107 * then the next attempt to receive data from that node will be omitted.
108 */
109 virtual void EventReadyToSend(NodeId node_id, bool& cancel_recv) = 0;
110
111 /**
112 * Called when new data has been received.
113 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node for which the data arrived.
114 * [@param](/bitcoin-bitcoin/contributor/param/)[in] data Data buffer.
115 * [@param](/bitcoin-bitcoin/contributor/param/)[in] n Number of bytes in `data`.
116 */
117 virtual void EventGotData(NodeId node_id, const uint8_t* data, size_t n) = 0;
118
119 /**
120 * Called when the remote peer has sent an EOF on the socket. This is a graceful
121 * close of their writing side, we can still send and they will receive, if it
122 * makes sense at the application level.
123 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node whose socket got EOF.
124 */
125 virtual void EventGotEOF(NodeId node_id) = 0;
126
127 /**
128 * Called when we get an irrecoverable error trying to read from a socket.
129 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node whose socket got an error.
130 * [@param](/bitcoin-bitcoin/contributor/param/)[in] errmsg Message describing the error.
131 */
132 virtual void EventGotPermanentReadError(NodeId node_id, const std::string& errmsg) = 0;
133
134 //
135 // Non-pure virtual functions can be overridden by children classes or left
136 // alone to use the default implementation from SockMan.
137 //
138
139 /**
140 * SockMan would only call EventReadyToSend() if this returns true.
141 * Can be used to temporary pause sends for a node.
142 * The implementation in SockMan always returns true.
143 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node for which to confirm or cancel a call to EventReadyToSend().
144 */
145 virtual bool ShouldTryToSend(NodeId node_id) const;
146
147 /**
148 * SockMan would only call Recv() on a node's socket if this returns true.
149 * Can be used to temporary pause receives for a node.
150 * The implementation in SockMan always returns true.
151 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node for which to confirm or cancel a receive.
152 */
153 virtual bool ShouldTryToRecv(NodeId node_id) const;
154
155 /**
156 * SockMan has completed the current send+recv iteration for a node.
157 * It will do another send+recv for this node after processing all other nodes.
158 * Can be used to execute periodic tasks for a given node.
159 * The implementation in SockMan does nothing.
160 * [@param](/bitcoin-bitcoin/contributor/param/)[in] node_id Node for which send+recv has been done.
161 */
162 virtual void EventIOLoopCompletedForNode(NodeId node_id);
163
164 /**
165 * SockMan has completed send+recv for all nodes.
166 * Can be used to execute periodic tasks for all nodes.
167 * The implementation in SockMan does nothing.
168 */
169 virtual void EventIOLoopCompletedForAllPeers();
170
171 /**
172 * Be notified of a change in the state of listening for incoming I2P connections.
173 * The default behavior, implemented by `SockMan`, is to ignore this event.
174 * [@param](/bitcoin-bitcoin/contributor/param/)[in] addr Our listening address.
175 * [@param](/bitcoin-bitcoin/contributor/param/)[in] success If true then the listen succeeded and we are now
176 * listening for incoming I2P connections at `addr`. If false then the
177 * call failed and now we are not listening (even if this was invoked
178 * before with `true`).
179 */
180 virtual void EventI2PListen(const CService& addr, bool success);
181};
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
.