// Copyright (c) 2020-2020 The Bitcoin Core developers // Copyright (c) 2016-2024 The Hush developers // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html #ifndef HUSH_I2P_H #define HUSH_I2P_H #include #include #include #include #include #include #include #include #include #include namespace i2p { /** * Binary data. */ using Binary = std::vector; /** * An established connection with another peer. */ struct Connection { /** Connected socket. */ std::unique_ptr 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. */ Session(const fs::path& private_key_file, const CService& control_host); /** * 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(); /** * Check the control sock and restart if needed */ bool Check(); /** * 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); /** * 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); protected: CCriticalSection cs_i2p; 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> 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; }; /** * 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 Hello() const EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); /** * Check the control socket for errors and possibly disconnect. */ void CheckControlSock(); /** * 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(cs_i2p); /** * 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(cs_i2p); /** * 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(cs_i2p); /** * 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(cs_i2p); /** * 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 StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); /** * Destroy the session, closing the internally used sockets. */ void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); /** * 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; /** * 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(cs_i2p); /** * 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 m_control_sock GUARDED_BY(cs_i2p); /** * Our .b32.i2p address. * Derived from `m_private_key`. */ CService m_my_addr GUARDED_BY(cs_i2p); /** * SAM session id. */ std::string m_session_id GUARDED_BY(cs_i2p); }; } // namespace sam } // namespace i2p #endif /* HUSH_I2P_H */