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. Notice: only a pointer to the
67 : : * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
68 : : * `Session` object.
69 : : */
70 : : Session(const fs::path& private_key_file,
71 : : const Proxy& control_host,
72 : : CThreadInterrupt* interrupt);
73 : :
74 : : /**
75 : : * Construct a transient session which will generate its own I2P private key
76 : : * rather than read the one from disk (it will not be saved on disk either and
77 : : * will be lost once this object is destroyed). This will not initiate any IO,
78 : : * the session will be lazily created later when first used.
79 : : * @param[in] control_host Location of the SAM proxy.
80 : : * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as
81 : : * possible and executing methods throw an exception. Notice: only a pointer to the
82 : : * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
83 : : * `Session` object.
84 : : */
85 : : Session(const Proxy& control_host, CThreadInterrupt* interrupt);
86 : :
87 : : /**
88 : : * Destroy the session, closing the internally used sockets. The sockets that have been
89 : : * returned by `Accept()` or `Connect()` will not be closed, but they will be closed by
90 : : * the SAM proxy because the session is destroyed. So they will return an error next time
91 : : * we try to read or write to them.
92 : : */
93 : : ~Session();
94 : :
95 : : /**
96 : : * Start listening for an incoming connection.
97 : : * @param[out] conn Upon successful completion the `sock` and `me` members will be set
98 : : * to the listening socket and address.
99 : : * @return true on success
100 : : */
101 : : bool Listen(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
102 : :
103 : : /**
104 : : * Wait for and accept a new incoming connection.
105 : : * @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful
106 : : * completion the `peer` member will be set to the address of the incoming peer.
107 : : * @return true on success
108 : : */
109 : : bool Accept(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
110 : :
111 : : /**
112 : : * Connect to an I2P peer.
113 : : * @param[in] to Peer to connect to.
114 : : * @param[out] conn Established connection. Only set if `true` is returned.
115 : : * @param[out] proxy_error If an error occurs due to proxy or general network failure, then
116 : : * this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then
117 : : * it is set to `false`. Only set if `false` is returned.
118 : : * @return true on success
119 : : */
120 : : bool Connect(const CService& to, Connection& conn, bool& proxy_error) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
121 : :
122 : : private:
123 : : /**
124 : : * A reply from the SAM proxy.
125 : : */
126 [ + - ]: 32 : struct Reply {
127 : : /**
128 : : * Full, unparsed reply.
129 : : */
130 : : std::string full;
131 : :
132 : : /**
133 : : * Request, used for detailed error reporting.
134 : : */
135 : : std::string request;
136 : :
137 : : /**
138 : : * A map of keywords from the parsed reply.
139 : : * For example, if the reply is "A=X B C=YZ", then the map will be
140 : : * keys["A"] == "X"
141 : : * keys["B"] == (empty std::optional)
142 : : * keys["C"] == "YZ"
143 : : */
144 : : std::unordered_map<std::string, std::optional<std::string>> keys;
145 : :
146 : : /**
147 : : * Get the value of a given key.
148 : : * For example if the reply is "A=X B" then:
149 : : * Value("A") -> "X"
150 : : * Value("B") -> throws
151 : : * Value("C") -> throws
152 : : * @param[in] key Key whose value to retrieve
153 : : * @returns the key's value
154 : : * @throws std::runtime_error if the key is not present or if it has no value
155 : : */
156 : : std::string Get(const std::string& key) const;
157 : : };
158 : :
159 : : /**
160 : : * Send request and get a reply from the SAM proxy.
161 : : * @param[in] sock A socket that is connected to the SAM proxy.
162 : : * @param[in] request Raw request to send, a newline terminator is appended to it.
163 : : * @param[in] check_result_ok If true then after receiving the reply a check is made
164 : : * whether it contains "RESULT=OK" and an exception is thrown if it does not.
165 : : * @throws std::runtime_error if an error occurs
166 : : */
167 : : Reply SendRequestAndGetReply(const Sock& sock,
168 : : const std::string& request,
169 : : bool check_result_ok = true) const;
170 : :
171 : : /**
172 : : * Open a new connection to the SAM proxy.
173 : : * @return a connected socket
174 : : * @throws std::runtime_error if an error occurs
175 : : */
176 : : std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
177 : :
178 : : /**
179 : : * Check the control socket for errors and possibly disconnect.
180 : : */
181 : : void CheckControlSock() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
182 : :
183 : : /**
184 : : * Generate a new destination with the SAM proxy and set `m_private_key` to it.
185 : : * @param[in] sock Socket to use for talking to the SAM proxy.
186 : : * @throws std::runtime_error if an error occurs
187 : : */
188 : : void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
189 : :
190 : : /**
191 : : * Generate a new destination with the SAM proxy, set `m_private_key` to it and save
192 : : * it on disk to `m_private_key_file`.
193 : : * @param[in] sock Socket to use for talking to the SAM proxy.
194 : : * @throws std::runtime_error if an error occurs
195 : : */
196 : : void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
197 : :
198 : : /**
199 : : * Derive own destination from `m_private_key`.
200 : : * @see https://geti2p.net/spec/common-structures#destination
201 : : * @return an I2P destination
202 : : */
203 : : Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
204 : :
205 : : /**
206 : : * Create the session if not already created. Reads the private key file and connects to the
207 : : * SAM proxy.
208 : : * @throws std::runtime_error if an error occurs
209 : : */
210 : : void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
211 : :
212 : : /**
213 : : * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing
214 : : * session id.
215 : : * @return the idle socket that is waiting for a peer to connect to us
216 : : * @throws std::runtime_error if an error occurs
217 : : */
218 : : std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
219 : :
220 : : /**
221 : : * Destroy the session, closing the internally used sockets.
222 : : */
223 : : void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
224 : :
225 : : /**
226 : : * The name of the file where this peer's private key is stored (in binary).
227 : : */
228 : : const fs::path m_private_key_file;
229 : :
230 : : /**
231 : : * The SAM control service proxy.
232 : : */
233 : : const Proxy m_control_host;
234 : :
235 : : /**
236 : : * Cease network activity when this is signaled.
237 : : */
238 : : CThreadInterrupt* const m_interrupt;
239 : :
240 : : /**
241 : : * Mutex protecting the members that can be concurrently accessed.
242 : : */
243 : : mutable Mutex m_mutex;
244 : :
245 : : /**
246 : : * The private key of this peer.
247 : : * @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3
248 : : */
249 : : Binary m_private_key GUARDED_BY(m_mutex);
250 : :
251 : : /**
252 : : * SAM control socket.
253 : : * Used to connect to the I2P SAM service and create a session
254 : : * ("SESSION CREATE"). With the established session id we later open
255 : : * other connections to the SAM service to accept incoming I2P
256 : : * connections and make outgoing ones.
257 : : * If not connected then this unique_ptr will be empty.
258 : : * See https://geti2p.net/en/docs/api/samv3
259 : : */
260 : : std::unique_ptr<Sock> m_control_sock GUARDED_BY(m_mutex);
261 : :
262 : : /**
263 : : * Our .b32.i2p address.
264 : : * Derived from `m_private_key`.
265 : : */
266 : : CService m_my_addr GUARDED_BY(m_mutex);
267 : :
268 : : /**
269 : : * SAM session id.
270 : : */
271 : : std::string m_session_id GUARDED_BY(m_mutex);
272 : :
273 : : /**
274 : : * Whether this is a transient session (the I2P private key will not be
275 : : * read or written to disk).
276 : : */
277 : : const bool m_transient;
278 : : };
279 : :
280 : : } // namespace sam
281 : : } // namespace i2p
282 : :
283 : : #endif // BITCOIN_I2P_H
|