Line data Source code
1 : // Copyright (c) 2020-2021 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 <fs.h> 10 : #include <netaddress.h> 11 : #include <netbase.h> 12 : #include <sync.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 23 : 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 // BITCOIN_I2P_H