aboutsummaryrefslogtreecommitdiff
path: root/src/i2p.h
diff options
context:
space:
mode:
authorVasil Dimov <vd@FreeBSD.org>2020-11-27 13:59:26 +0100
committerVasil Dimov <vd@FreeBSD.org>2021-03-01 18:19:37 +0100
commitc22daa2ecff1acd25426cd46f98f2587d1d324c3 (patch)
treef8fca7ea86ea24f19ef0d498c4ed4ef15a43feba /src/i2p.h
parent5bac7e45e1d3a07115b5ff002d988438fcc92a53 (diff)
downloadbitcoin-c22daa2ecff1acd25426cd46f98f2587d1d324c3.tar.xz
net: implement the necessary parts of the I2P SAM protocol
Implement the following commands from the I2P SAM protocol: * HELLO: needed for all of the remaining ones * DEST GENERATE: to generate our private key and destination * NAMING LOOKUP: to convert .i2p addresses to destinations * SESSION CREATE: needed for STREAM CONNECT and STREAM ACCEPT * STREAM CONNECT: to make outgoing connections * STREAM ACCEPT: to accept incoming connections
Diffstat (limited to 'src/i2p.h')
-rw-r--r--src/i2p.h260
1 files changed, 260 insertions, 0 deletions
diff --git a/src/i2p.h b/src/i2p.h
new file mode 100644
index 0000000000..8fafe0a4d0
--- /dev/null
+++ b/src/i2p.h
@@ -0,0 +1,260 @@
+// Copyright (c) 2020-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_I2P_H
+#define BITCOIN_I2P_H
+
+#include <compat.h>
+#include <fs.h>
+#include <netaddress.h>
+#include <sync.h>
+#include <threadinterrupt.h>
+#include <util/sock.h>
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace i2p {
+
+/**
+ * Binary data.
+ */
+using Binary = std::vector<uint8_t>;
+
+/**
+ * An established connection with another peer.
+ */
+struct Connection {
+ /** Connected socket. */
+ Sock sock;
+
+ /** Our I2P address. */
+ CService me;
+
+ /** The peer's I2P address. */
+ CService peer;
+};
+
+namespace sam {
+
+/**
+ * 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.
+ * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as
+ * possible and executing methods throw an exception. Notice: only a pointer to the
+ * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this
+ * `Session` object.
+ */
+ Session(const fs::path& private_key_file,
+ const CService& control_host,
+ CThreadInterrupt* interrupt);
+
+ /**
+ * 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();
+
+ /**
+ * 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);
+
+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<std::string, std::optional<std::string>> 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;
+ };
+
+ /**
+ * Log a message in the `BCLog::I2P` category.
+ * @param[in] fmt printf(3)-like format string.
+ * @param[in] args printf(3)-like arguments that correspond to `fmt`.
+ */
+ template <typename... Args>
+ void Log(const std::string& fmt, const Args&... args) 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
+ */
+ Sock Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
+
+ /**
+ * 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(m_mutex);
+
+ /**
+ * 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(m_mutex);
+
+ /**
+ * 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(m_mutex);
+
+ /**
+ * 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(m_mutex);
+
+ /**
+ * 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
+ */
+ Sock StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
+
+ /**
+ * Destroy the session, closing the internally used sockets.
+ */
+ void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
+
+ /**
+ * 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;
+
+ /**
+ * Cease network activity when this is signaled.
+ */
+ CThreadInterrupt* const m_interrupt;
+
+ /**
+ * Mutex protecting the members that can be concurrently accessed.
+ */
+ mutable Mutex m_mutex;
+
+ /**
+ * 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(m_mutex);
+
+ /**
+ * 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
+ */
+ Sock m_control_sock GUARDED_BY(m_mutex);
+
+ /**
+ * Our .b32.i2p address.
+ * Derived from `m_private_key`.
+ */
+ CService m_my_addr GUARDED_BY(m_mutex);
+
+ /**
+ * SAM session id.
+ */
+ std::string m_session_id GUARDED_BY(m_mutex);
+};
+
+} // namespace sam
+} // namespace i2p
+
+#endif /* BITCOIN_I2P_H */