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