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. The aim is to (re)use that for Stratum V2 and for libevent-less RPC/HTTP server.
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
.