1 // Copyright (c) 2020-present 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 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
280