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