1 // Copyright (c) 2020-2022 The Limenka 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 LIMENKA_I2P_H
6 #define LIMENKA_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 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 // LIMENKA_I2P_H
284