mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05: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:
parent
5bac7e45e1
commit
c22daa2ecf
5 changed files with 671 additions and 0 deletions
|
@ -148,6 +148,7 @@ BITCOIN_CORE_H = \
|
|||
fs.h \
|
||||
httprpc.h \
|
||||
httpserver.h \
|
||||
i2p.h \
|
||||
index/base.h \
|
||||
index/blockfilterindex.h \
|
||||
index/disktxpos.h \
|
||||
|
@ -315,6 +316,7 @@ libbitcoin_server_a_SOURCES = \
|
|||
flatfile.cpp \
|
||||
httprpc.cpp \
|
||||
httpserver.cpp \
|
||||
i2p.cpp \
|
||||
index/base.cpp \
|
||||
index/blockfilterindex.cpp \
|
||||
index/txindex.cpp \
|
||||
|
|
407
src/i2p.cpp
Normal file
407
src/i2p.cpp
Normal file
|
@ -0,0 +1,407 @@
|
|||
// 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.
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <compat.h>
|
||||
#include <compat/endian.h>
|
||||
#include <crypto/sha256.h>
|
||||
#include <fs.h>
|
||||
#include <i2p.h>
|
||||
#include <logging.h>
|
||||
#include <netaddress.h>
|
||||
#include <netbase.h>
|
||||
#include <random.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/readwritefile.h>
|
||||
#include <util/sock.h>
|
||||
#include <util/spanparsing.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace i2p {
|
||||
|
||||
/**
|
||||
* Swap Standard Base64 <-> I2P Base64.
|
||||
* Standard Base64 uses `+` and `/` as last two characters of its alphabet.
|
||||
* I2P Base64 uses `-` and `~` respectively.
|
||||
* So it is easy to detect in which one is the input and convert to the other.
|
||||
* @param[in] from Input to convert.
|
||||
* @return converted `from`
|
||||
*/
|
||||
static std::string SwapBase64(const std::string& from)
|
||||
{
|
||||
std::string to;
|
||||
to.resize(from.size());
|
||||
for (size_t i = 0; i < from.size(); ++i) {
|
||||
switch (from[i]) {
|
||||
case '-':
|
||||
to[i] = '+';
|
||||
break;
|
||||
case '~':
|
||||
to[i] = '/';
|
||||
break;
|
||||
case '+':
|
||||
to[i] = '-';
|
||||
break;
|
||||
case '/':
|
||||
to[i] = '~';
|
||||
break;
|
||||
default:
|
||||
to[i] = from[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an I2P-style Base64 string.
|
||||
* @param[in] i2p_b64 I2P-style Base64 string.
|
||||
* @return decoded `i2p_b64`
|
||||
* @throw std::runtime_error if decoding fails
|
||||
*/
|
||||
static Binary DecodeI2PBase64(const std::string& i2p_b64)
|
||||
{
|
||||
const std::string& std_b64 = SwapBase64(i2p_b64);
|
||||
bool invalid;
|
||||
Binary decoded = DecodeBase64(std_b64.c_str(), &invalid);
|
||||
if (invalid) {
|
||||
throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the .b32.i2p address of an I2P destination (binary).
|
||||
* @param[in] dest I2P destination.
|
||||
* @return the address that corresponds to `dest`
|
||||
* @throw std::runtime_error if conversion fails
|
||||
*/
|
||||
static CNetAddr DestBinToAddr(const Binary& dest)
|
||||
{
|
||||
CSHA256 hasher;
|
||||
hasher.Write(dest.data(), dest.size());
|
||||
unsigned char hash[CSHA256::OUTPUT_SIZE];
|
||||
hasher.Finalize(hash);
|
||||
|
||||
CNetAddr addr;
|
||||
const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p";
|
||||
if (!addr.SetSpecial(addr_str)) {
|
||||
throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the .b32.i2p address of an I2P destination (I2P-style Base64).
|
||||
* @param[in] dest I2P destination.
|
||||
* @return the address that corresponds to `dest`
|
||||
* @throw std::runtime_error if conversion fails
|
||||
*/
|
||||
static CNetAddr DestB64ToAddr(const std::string& dest)
|
||||
{
|
||||
const Binary& decoded = DecodeI2PBase64(dest);
|
||||
return DestBinToAddr(decoded);
|
||||
}
|
||||
|
||||
namespace sam {
|
||||
|
||||
Session::Session(const fs::path& private_key_file,
|
||||
const CService& control_host,
|
||||
CThreadInterrupt* interrupt)
|
||||
: m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt)
|
||||
{
|
||||
}
|
||||
|
||||
Session::~Session()
|
||||
{
|
||||
LOCK(m_mutex);
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
bool Session::Listen(Connection& conn)
|
||||
{
|
||||
try {
|
||||
LOCK(m_mutex);
|
||||
CreateIfNotCreatedAlready();
|
||||
conn.me = m_my_addr;
|
||||
conn.sock = StreamAccept();
|
||||
return true;
|
||||
} catch (const std::runtime_error& e) {
|
||||
Log("Error listening: %s", e.what());
|
||||
CheckControlSock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Session::Accept(Connection& conn)
|
||||
{
|
||||
try {
|
||||
while (!*m_interrupt) {
|
||||
Sock::Event occurred;
|
||||
conn.sock.Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred);
|
||||
|
||||
if ((occurred & Sock::RECV) == 0) {
|
||||
// Timeout, no incoming connections within MAX_WAIT_FOR_IO.
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& peer_dest =
|
||||
conn.sock.RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt);
|
||||
|
||||
conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort());
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (const std::runtime_error& e) {
|
||||
Log("Error accepting: %s", e.what());
|
||||
CheckControlSock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
|
||||
{
|
||||
proxy_error = true;
|
||||
|
||||
std::string session_id;
|
||||
Sock sock;
|
||||
conn.peer = to;
|
||||
|
||||
try {
|
||||
{
|
||||
LOCK(m_mutex);
|
||||
CreateIfNotCreatedAlready();
|
||||
session_id = m_session_id;
|
||||
conn.me = m_my_addr;
|
||||
sock = Hello();
|
||||
}
|
||||
|
||||
const Reply& lookup_reply =
|
||||
SendRequestAndGetReply(sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP()));
|
||||
|
||||
const std::string& dest = lookup_reply.Get("VALUE");
|
||||
|
||||
const Reply& connect_reply = SendRequestAndGetReply(
|
||||
sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
|
||||
false);
|
||||
|
||||
const std::string& result = connect_reply.Get("RESULT");
|
||||
|
||||
if (result == "OK") {
|
||||
conn.sock = std::move(sock);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result == "INVALID_ID") {
|
||||
LOCK(m_mutex);
|
||||
Disconnect();
|
||||
throw std::runtime_error("Invalid session id");
|
||||
}
|
||||
|
||||
if (result == "CANT_REACH_PEER" || result == "TIMEOUT") {
|
||||
proxy_error = false;
|
||||
}
|
||||
|
||||
throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
|
||||
} catch (const std::runtime_error& e) {
|
||||
Log("Error connecting to %s: %s", to.ToString(), e.what());
|
||||
CheckControlSock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
std::string Session::Reply::Get(const std::string& key) const
|
||||
{
|
||||
const auto& pos = keys.find(key);
|
||||
if (pos == keys.end() || !pos->second.has_value()) {
|
||||
throw std::runtime_error(
|
||||
strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));
|
||||
}
|
||||
return pos->second.value();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void Session::Log(const std::string& fmt, const Args&... args) const
|
||||
{
|
||||
LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...));
|
||||
}
|
||||
|
||||
Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
|
||||
const std::string& request,
|
||||
bool check_result_ok) const
|
||||
{
|
||||
sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt);
|
||||
|
||||
Reply reply;
|
||||
|
||||
// Don't log the full "SESSION CREATE ..." because it contains our private key.
|
||||
reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request;
|
||||
|
||||
// It could take a few minutes for the I2P router to reply as it is querying the I2P network
|
||||
// (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking
|
||||
// `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is
|
||||
// signaled.
|
||||
static constexpr auto recv_timeout = 3min;
|
||||
|
||||
reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt);
|
||||
|
||||
for (const auto& kv : spanparsing::Split(reply.full, ' ')) {
|
||||
const auto& pos = std::find(kv.begin(), kv.end(), '=');
|
||||
if (pos != kv.end()) {
|
||||
reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
|
||||
} else {
|
||||
reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
if (check_result_ok && reply.Get("RESULT") != "OK") {
|
||||
throw std::runtime_error(
|
||||
strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
Sock Session::Hello() const
|
||||
{
|
||||
auto sock = CreateSock(m_control_host);
|
||||
|
||||
if (!sock) {
|
||||
throw std::runtime_error("Cannot create socket");
|
||||
}
|
||||
|
||||
if (!ConnectSocketDirectly(m_control_host, sock->Get(), nConnectTimeout, true)) {
|
||||
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
|
||||
}
|
||||
|
||||
SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
|
||||
|
||||
return std::move(*sock);
|
||||
}
|
||||
|
||||
void Session::CheckControlSock()
|
||||
{
|
||||
LOCK(m_mutex);
|
||||
|
||||
std::string errmsg;
|
||||
if (!m_control_sock.IsConnected(errmsg)) {
|
||||
Log("Control socket error: %s", errmsg);
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::DestGenerate(const Sock& sock)
|
||||
{
|
||||
// https://geti2p.net/spec/common-structures#key-certificates
|
||||
// "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations".
|
||||
// Use "7" because i2pd <2.24.0 does not recognize the textual form.
|
||||
const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
|
||||
|
||||
m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
|
||||
}
|
||||
|
||||
void Session::GenerateAndSavePrivateKey(const Sock& sock)
|
||||
{
|
||||
DestGenerate(sock);
|
||||
|
||||
// umask is set to 077 in init.cpp, which is ok (unless -sysperms is given)
|
||||
if (!WriteBinaryFile(m_private_key_file,
|
||||
std::string(m_private_key.begin(), m_private_key.end()))) {
|
||||
throw std::runtime_error(
|
||||
strprintf("Cannot save I2P private key to %s", m_private_key_file));
|
||||
}
|
||||
}
|
||||
|
||||
Binary Session::MyDestination() const
|
||||
{
|
||||
// From https://geti2p.net/spec/common-structures#destination:
|
||||
// "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be
|
||||
// non-zero"
|
||||
static constexpr size_t DEST_LEN_BASE = 387;
|
||||
static constexpr size_t CERT_LEN_POS = 385;
|
||||
|
||||
uint16_t cert_len;
|
||||
memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len));
|
||||
cert_len = be16toh(cert_len);
|
||||
|
||||
const size_t dest_len = DEST_LEN_BASE + cert_len;
|
||||
|
||||
return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
|
||||
}
|
||||
|
||||
void Session::CreateIfNotCreatedAlready()
|
||||
{
|
||||
std::string errmsg;
|
||||
if (m_control_sock.IsConnected(errmsg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("Creating SAM session with %s", m_control_host.ToString());
|
||||
|
||||
Sock sock = Hello();
|
||||
|
||||
const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
|
||||
if (read_ok) {
|
||||
m_private_key.assign(data.begin(), data.end());
|
||||
} else {
|
||||
GenerateAndSavePrivateKey(sock);
|
||||
}
|
||||
|
||||
const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
|
||||
const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
|
||||
|
||||
SendRequestAndGetReply(sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
|
||||
session_id, private_key_b64));
|
||||
|
||||
m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort());
|
||||
m_session_id = session_id;
|
||||
m_control_sock = std::move(sock);
|
||||
|
||||
LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id,
|
||||
m_my_addr.ToString());
|
||||
}
|
||||
|
||||
Sock Session::StreamAccept()
|
||||
{
|
||||
Sock sock = Hello();
|
||||
|
||||
const Reply& reply = SendRequestAndGetReply(
|
||||
sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
|
||||
|
||||
const std::string& result = reply.Get("RESULT");
|
||||
|
||||
if (result == "OK") {
|
||||
return sock;
|
||||
}
|
||||
|
||||
if (result == "INVALID_ID") {
|
||||
// If our session id is invalid, then force session re-creation on next usage.
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
throw std::runtime_error(strprintf("\"%s\"", reply.full));
|
||||
}
|
||||
|
||||
void Session::Disconnect()
|
||||
{
|
||||
if (m_control_sock.Get() != INVALID_SOCKET) {
|
||||
if (m_session_id.empty()) {
|
||||
Log("Destroying incomplete session");
|
||||
} else {
|
||||
Log("Destroying session %s", m_session_id);
|
||||
}
|
||||
}
|
||||
m_control_sock.Reset();
|
||||
m_session_id.clear();
|
||||
}
|
||||
} // namespace sam
|
||||
} // namespace i2p
|
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 */
|
|
@ -156,6 +156,7 @@ const CLogCategoryDesc LogCategories[] =
|
|||
{BCLog::QT, "qt"},
|
||||
{BCLog::LEVELDB, "leveldb"},
|
||||
{BCLog::VALIDATION, "validation"},
|
||||
{BCLog::I2P, "i2p"},
|
||||
{BCLog::ALL, "1"},
|
||||
{BCLog::ALL, "all"},
|
||||
};
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace BCLog {
|
|||
QT = (1 << 19),
|
||||
LEVELDB = (1 << 20),
|
||||
VALIDATION = (1 << 21),
|
||||
I2P = (1 << 22),
|
||||
ALL = ~(uint32_t)0,
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue