i2p.h raw

   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