mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 14:38:29 +01:00
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
This commit is contained in:
260
src/i2p.h
Normal file
260
src/i2p.h
Normal file
@@ -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 */
|
||||
Reference in New Issue
Block a user