Branch data Line data Source code
1 : : // Copyright (c) 2020-2022 The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_I2P_H
6 : : #define BITCOIN_I2P_H
7 : :
8 : : #include <compat/compat.h>
9 : : #include <netaddress.h>
10 : : #include <netbase.h>
11 : : #include <sync.h>
12 : : #include <util/fs.h>
13 : : #include <util/sock.h>
14 : : #include <util/threadinterrupt.h>
15 : :
16 : : #include <memory>
17 : : #include <optional>
18 : : #include <string>
19 : : #include <unordered_map>
20 : : #include <vector>
21 : :
22 : : namespace i2p {
23 : :
24 : : /**
25 : : * Binary data.
26 : : */
27 : : using Binary = std::vector<uint8_t>;
28 : :
29 : : /**
30 : : * An established connection with another peer.
31 : : */
32 : : struct Connection {
33 : : /** Connected socket. */
34 : : std::unique_ptr<Sock> sock;
35 : :
36 : : /** Our I2P address. */
37 : : CService me;
38 : :
39 : : /** The peer's I2P address. */
40 : : CService peer;
41 : : };
42 : :
43 : : namespace sam {
44 : :
45 : : /**
46 : : * The maximum size of an incoming message from the I2P SAM proxy (in bytes).
47 : : * Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator.
48 : : * The longest known message is ~1400 bytes, so this is high enough not to be triggered during
49 : : * normal operation, yet low enough to avoid a malicious proxy from filling our memory.
50 : : */
51 : : static constexpr size_t MAX_MSG_SIZE{65536};
52 : :
53 : : /**
54 : : * I2P SAM session.
55 : : */
56 : : class Session
57 : : {
58 : : public:
59 : : /**
60 : : * Construct a session. This will not initiate any IO, the session will be lazily created
61 : : * later when first used.
62 : : * @param[in] private_key_file Path to a private key file. If the file does not exist then the
63 : : * private key will be generated and saved into the file.
64 : : * @param[in] control_host Location of the SAM proxy.
65 : : * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as
66 : : * possible and executing methods throw an exception.
67 : : */
68 : : Session(const fs::path& private_key_file,
69 : : const Proxy& control_host,
70 : : std::shared_ptr<CThreadInterrupt> interrupt);
71 : :
72 : : /**
73 : : * Construct a transient session which will generate its own I2P private key
74 : : * rather than read the one from disk (it will not be saved on disk either and
75 : : * will be lost once this object is destroyed). This will not initiate any IO,
76 : : * the session will be lazily created later when first used.
77 : : * @param[in] control_host Location of the SAM proxy.
78 : : * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as
79 : : * possible and executing methods throw an exception.
80 : : */
81 : : Session(const Proxy& control_host, std::shared_ptr<CThreadInterrupt> interrupt);
82 : :
83 : : /**
84 : : * Destroy the session, closing the internally used sockets. The sockets that have been
85 : : * returned by `Accept()` or `Connect()` will not be closed, but they will be closed by
86 : : * the SAM proxy because the session is destroyed. So they will return an error next time
87 : : * we try to read or write to them.
88 : : */
89 : : ~Session();
90 : :
91 : : /**
92 : : * Start listening for an incoming connection.
93 : : * @param[out] conn Upon successful completion the `sock` and `me` members will be set
94 : : * to the listening socket and address.
95 : : * @return true on success
96 : : */
97 : : bool Listen(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
98 : :
99 : : /**
100 : : * Wait for and accept a new incoming connection.
101 : : * @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful
102 : : * completion the `peer` member will be set to the address of the incoming peer.
103 : : * @return true on success
104 : : */
105 : : bool Accept(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
106 : :
107 : : /**
108 : : * Connect to an I2P peer.
109 : : * @param[in] to Peer to connect to.
110 : : * @param[out] conn Established connection. Only set if `true` is returned.
111 : : * @param[out] proxy_error If an error occurs due to proxy or general network failure, then
112 : : * this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then
113 : : * it is set to `false`. Only set if `false` is returned.
114 : : * @return true on success
115 : : */
116 : : bool Connect(const CService& to, Connection& conn, bool& proxy_error) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
117 : :
118 : : private:
119 : : /**
120 : : * A reply from the SAM proxy.
121 : : */
122 [ - + ]: 32 : struct Reply {
123 : : /**
124 : : * Full, unparsed reply.
125 : : */
126 : : std::string full;
127 : :
128 : : /**
129 : : * Request, used for detailed error reporting.
130 : : */
131 : : std::string request;
132 : :
133 : : /**
134 : : * A map of keywords from the parsed reply.
135 : : * For example, if the reply is "A=X B C=YZ", then the map will be
136 : : * keys["A"] == "X"
137 : : * keys["B"] == (empty std::optional)
138 : : * keys["C"] == "YZ"
139 : : */
140 : : std::unordered_map<std::string, std::optional<std::string>> keys;
141 : :
142 : : /**
143 : : * Get the value of a given key.
144 : : * For example if the reply is "A=X B" then:
145 : : * Value("A") -> "X"
146 : : * Value("B") -> throws
147 : : * Value("C") -> throws
148 : : * @param[in] key Key whose value to retrieve
149 : : * @returns the key's value
150 : : * @throws std::runtime_error if the key is not present or if it has no value
151 : : */
152 : : std::string Get(const std::string& key) const;
153 : : };
154 : :
155 : : /**
156 : : * Send request and get a reply from the SAM proxy.
157 : : * @param[in] sock A socket that is connected to the SAM proxy.
158 : : * @param[in] request Raw request to send, a newline terminator is appended to it.
159 : : * @param[in] check_result_ok If true then after receiving the reply a check is made
160 : : * whether it contains "RESULT=OK" and an exception is thrown if it does not.
161 : : * @throws std::runtime_error if an error occurs
162 : : */
163 : : Reply SendRequestAndGetReply(const Sock& sock,
164 : : const std::string& request,
165 : : bool check_result_ok = true) const;
166 : :
167 : : /**
168 : : * Open a new connection to the SAM proxy.
169 : : * @return a connected socket
170 : : * @throws std::runtime_error if an error occurs
171 : : */
172 : : std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
173 : :
174 : : /**
175 : : * Check the control socket for errors and possibly disconnect.
176 : : */
177 : : void CheckControlSock() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
178 : :
179 : : /**
180 : : * Generate a new destination with the SAM proxy and set `m_private_key` to it.
181 : : * @param[in] sock Socket to use for talking to the SAM proxy.
182 : : * @throws std::runtime_error if an error occurs
183 : : */
184 : : void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
185 : :
186 : : /**
187 : : * Generate a new destination with the SAM proxy, set `m_private_key` to it and save
188 : : * it on disk to `m_private_key_file`.
189 : : * @param[in] sock Socket to use for talking to the SAM proxy.
190 : : * @throws std::runtime_error if an error occurs
191 : : */
192 : : void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
193 : :
194 : : /**
195 : : * Derive own destination from `m_private_key`.
196 : : * @see https://geti2p.net/spec/common-structures#destination
197 : : * @return an I2P destination
198 : : */
199 : : Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
200 : :
201 : : /**
202 : : * Create the session if not already created. Reads the private key file and connects to the
203 : : * SAM proxy.
204 : : * @throws std::runtime_error if an error occurs
205 : : */
206 : : void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
207 : :
208 : : /**
209 : : * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing
210 : : * session id.
211 : : * @return the idle socket that is waiting for a peer to connect to us
212 : : * @throws std::runtime_error if an error occurs
213 : : */
214 : : std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
215 : :
216 : : /**
217 : : * Destroy the session, closing the internally used sockets.
218 : : */
219 : : void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
220 : :
221 : : /**
222 : : * The name of the file where this peer's private key is stored (in binary).
223 : : */
224 : : const fs::path m_private_key_file;
225 : :
226 : : /**
227 : : * The SAM control service proxy.
228 : : */
229 : : const Proxy m_control_host;
230 : :
231 : : /**
232 : : * Cease network activity when this is signaled.
233 : : */
234 : : const std::shared_ptr<CThreadInterrupt> m_interrupt;
235 : :
236 : : /**
237 : : * Mutex protecting the members that can be concurrently accessed.
238 : : */
239 : : mutable Mutex m_mutex;
240 : :
241 : : /**
242 : : * The private key of this peer.
243 : : * @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3
244 : : */
245 : : Binary m_private_key GUARDED_BY(m_mutex);
246 : :
247 : : /**
248 : : * SAM control socket.
249 : : * Used to connect to the I2P SAM service and create a session
250 : : * ("SESSION CREATE"). With the established session id we later open
251 : : * other connections to the SAM service to accept incoming I2P
252 : : * connections and make outgoing ones.
253 : : * If not connected then this unique_ptr will be empty.
254 : : * See https://geti2p.net/en/docs/api/samv3
255 : : */
256 : : std::unique_ptr<Sock> m_control_sock GUARDED_BY(m_mutex);
257 : :
258 : : /**
259 : : * Our .b32.i2p address.
260 : : * Derived from `m_private_key`.
261 : : */
262 : : CService m_my_addr GUARDED_BY(m_mutex);
263 : :
264 : : /**
265 : : * SAM session id.
266 : : */
267 : : std::string m_session_id GUARDED_BY(m_mutex);
268 : :
269 : : /**
270 : : * Whether this is a transient session (the I2P private key will not be
271 : : * read or written to disk).
272 : : */
273 : : const bool m_transient;
274 : : };
275 : :
276 : : } // namespace sam
277 : : } // namespace i2p
278 : :
279 : : #endif // BITCOIN_I2P_H
|