22#include <util/threadinterrupt.h>
45 to.resize(from.size());
46 for (
size_t i = 0; i < from.size(); ++i) {
76 const std::string& std_b64 =
SwapBase64(i2p_b64);
79 throw std::runtime_error(
strprintf(
"Cannot decode Base64: \"%s\"", i2p_b64));
81 return std::move(*decoded);
93 hasher.
Write(dest.data(), dest.size());
98 const std::string addr_str =
EncodeBase32(hash,
false) +
".b32.i2p";
100 throw std::runtime_error(
strprintf(
"Cannot parse I2P address: \"%s\"", addr_str));
121 const Proxy& control_host,
122 std::shared_ptr<CThreadInterrupt> interrupt)
123 : m_private_key_file{private_key_file},
124 m_control_host{control_host},
125 m_interrupt{interrupt},
131 : m_control_host{control_host},
132 m_interrupt{interrupt},
151 }
catch (
const std::runtime_error& e) {
152 LogError(
"Couldn't listen: %s\n", e.what());
163 bool disconnect{
false};
168 errmsg =
"wait on socket failed";
177 std::string peer_dest;
180 }
catch (
const std::runtime_error& e) {
188 }
catch (
const std::runtime_error& e) {
194 if (peer_dest.find(
"RESULT=I2P_ERROR") != std::string::npos) {
195 errmsg =
strprintf(
"unexpected reply that hints the session is unusable: %s", peer_dest);
211 LogDebug(
BCLog::I2P,
"Error accepting%s: %s\n", disconnect ?
" (will close the session)" :
"", errmsg);
234 std::string session_id;
235 std::unique_ptr<Sock> sock;
242 session_id = m_session_id;
247 const Reply& lookup_reply =
250 const std::string& dest = lookup_reply.
Get(
"VALUE");
253 *sock,
strprintf(
"STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
256 const std::string& result = connect_reply.
Get(
"RESULT");
258 if (result ==
"OK") {
259 conn.
sock = std::move(sock);
263 if (result ==
"INVALID_ID") {
266 throw std::runtime_error(
"Invalid session id");
269 if (result ==
"CANT_REACH_PEER" || result ==
"TIMEOUT") {
273 throw std::runtime_error(
strprintf(
"\"%s\"", connect_reply.
full));
274 }
catch (
const std::runtime_error& e) {
285 const auto& pos =
keys.find(key);
286 if (pos ==
keys.end() || !pos->second.has_value()) {
287 throw std::runtime_error(
290 return pos->second.value();
294 const std::string& request,
295 bool check_result_ok)
const
302 reply.
request = request.starts_with(
"SESSION CREATE") ?
"SESSION CREATE ..." : request;
308 static constexpr auto recv_timeout = 3min;
312 for (
const auto& kv :
Split(reply.
full,
' ')) {
313 const auto pos{std::ranges::find(kv,
'=')};
314 if (pos != kv.end()) {
315 reply.
keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
317 reply.
keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
321 if (check_result_ok && reply.
Get(
"RESULT") !=
"OK") {
322 throw std::runtime_error(
323 strprintf(
"Reply to \"%s\": had a RESULT not equal to OK.", reply.
request));
347 if (m_control_sock && !m_control_sock->IsConnected(errmsg)) {
370 std::string(m_private_key.begin(), m_private_key.end()))) {
371 throw std::runtime_error(
381 static constexpr size_t DEST_LEN_BASE = 387;
382 static constexpr size_t CERT_LEN_POS = 385;
386 if (m_private_key.size() < CERT_LEN_POS +
sizeof(cert_len)) {
387 throw std::runtime_error(
strprintf(
"The private key is too short (%d < %d)",
388 m_private_key.size(),
389 CERT_LEN_POS +
sizeof(cert_len)));
392 memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS),
sizeof(cert_len));
395 const size_t dest_len = DEST_LEN_BASE + cert_len;
397 if (dest_len > m_private_key.size()) {
398 throw std::runtime_error(
strprintf(
"Certificate length (%d) designates that the private key should "
399 "be %d bytes, but it is only %d bytes",
402 m_private_key.size()));
405 return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
411 if (m_control_sock && m_control_sock->IsConnected(errmsg)) {
415 const auto session_type =
m_transient ?
"transient" :
"persistent";
427 strprintf(
"SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7 "
428 "i2cp.leaseSetEncType=4,0 inbound.quantity=1 outbound.quantity=1",
437 m_private_key.assign(
data.begin(),
data.end());
445 strprintf(
"SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s "
446 "i2cp.leaseSetEncType=4,0 inbound.quantity=3 outbound.quantity=3",
452 m_session_id = session_id;
453 m_control_sock = std::move(sock);
455 LogInfo(
"%s I2P SAM session %s created, my address=%s",
458 m_my_addr.ToStringAddrPort());
466 *sock,
strprintf(
"STREAM ACCEPT ID=%s SILENT=false", m_session_id),
false);
468 const std::string& result = reply.
Get(
"RESULT");
470 if (result ==
"OK") {
474 if (result ==
"INVALID_ID") {
484 if (m_control_sock) {
485 if (m_session_id.empty()) {
486 LogInfo(
"Destroying incomplete I2P SAM session");
488 LogInfo(
"Destroying I2P SAM session %s", m_session_id);
490 m_control_sock.reset();
492 m_session_id.clear();
std::string ToStringAddr() const
bool SetSpecial(std::string_view addr)
Parse a Tor or I2P address and set this object to it.
A hasher class for SHA-256.
static const size_t OUTPUT_SIZE
void Finalize(unsigned char hash[OUTPUT_SIZE])
CSHA256 & Write(const unsigned char *data, size_t len)
A combination of a network address (CNetAddr) and a (TCP) port.
std::string ToStringAddrPort() const
std::string ToString() const
std::unique_ptr< Sock > Connect() const
RAII helper class that manages a socket and closes it automatically when it goes out of scope.
static constexpr Event RecvEvent
If passed to Wait(), then it will wait for readiness to read from the socket.
virtual void SendComplete(std::span< const unsigned char > data, std::chrono::milliseconds timeout, CThreadInterrupt &interrupt) const
Send the given data, retrying on transient errors.
virtual std::string RecvUntilTerminator(uint8_t terminator, std::chrono::milliseconds timeout, CThreadInterrupt &interrupt, size_t max_data) const
Read from socket until a terminator character is encountered.
std::string GetHex() const
const fs::path m_private_key_file
The name of the file where this peer's private key is stored (in binary).
Reply SendRequestAndGetReply(const Sock &sock, const std::string &request, bool check_result_ok=true) const
Send request and get a reply from the SAM proxy.
Session(const fs::path &private_key_file, const Proxy &control_host, std::shared_ptr< CThreadInterrupt > interrupt)
Construct a session.
bool Listen(Connection &conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Start listening for an incoming connection.
void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Create the session if not already created.
void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Destroy the session, closing the internally used sockets.
std::unique_ptr< Sock > Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Open a new connection to the SAM proxy.
void CheckControlSock() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check the control socket for errors and possibly disconnect.
std::unique_ptr< Sock > StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing session i...
Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Derive own destination from m_private_key.
~Session()
Destroy the session, closing the internally used sockets.
const bool m_transient
Whether this is a transient session (the I2P private key will not be read or written to disk).
const std::shared_ptr< CThreadInterrupt > m_interrupt
Cease network activity when this is signaled.
void GenerateAndSavePrivateKey(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_priva...
bool Connect(const CService &to, Connection &conn, bool &proxy_error) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Connect to an I2P peer.
void DestGenerate(const Sock &sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Generate a new destination with the SAM proxy and set m_private_key to it.
Mutex m_mutex
Mutex protecting the members that can be concurrently accessed.
bool Accept(Connection &conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Wait for and accept a new incoming connection.
const Proxy m_control_host
The SAM control service proxy.
BSWAP_CONSTEXPR uint16_t be16toh_internal(uint16_t big_endian_16bits)
static auto quoted(const std::string &s)
static std::string PathToString(const path &path)
Convert path object to a byte string.
#define LogDebug(category,...)
static constexpr size_t MAX_MSG_SIZE
The maximum size of an incoming message from the I2P SAM proxy (in bytes).
static CNetAddr DestB64ToAddr(const std::string &dest)
Derive the .b32.i2p address of an I2P destination (I2P-style Base64).
static std::string SwapBase64(const std::string &from)
Swap Standard Base64 <-> I2P Base64.
static CNetAddr DestBinToAddr(const Binary &dest)
Derive the .b32.i2p address of an I2P destination (binary).
std::vector< uint8_t > Binary
Binary data.
static Binary DecodeI2PBase64(const std::string &i2p_b64)
Decode an I2P-style Base64 string.
std::vector< T > Split(const std::span< const char > &sp, std::string_view separators, bool include_sep=false)
Split a string on any char found in separators, returning a vector.
static constexpr uint16_t I2P_SAM31_PORT
SAM 3.1 and earlier do not support specifying ports and force the port to 0.
uint256 GetRandHash() noexcept
Generate a random uint256.
bool WriteBinaryFile(const fs::path &filename, const std::string &data)
Write contents of std::string to a file.
std::pair< bool, std::string > ReadBinaryFile(const fs::path &filename, size_t maxsize)
Read full contents of a file and return them in a std::string.
static constexpr auto MAX_WAIT_FOR_IO
Maximum time to wait for I/O readiness.
An established connection with another peer.
std::unique_ptr< Sock > sock
Connected socket.
CService me
Our I2P address.
CService peer
The peer's I2P address.
A reply from the SAM proxy.
std::string Get(const std::string &key) const
Get the value of a given key.
std::string full
Full, unparsed reply.
std::unordered_map< std::string, std::optional< std::string > > keys
A map of keywords from the parsed reply.
std::string request
Request, used for detailed error reporting.
#define AssertLockNotHeld(cs)
std::string Capitalize(std::string str)
Capitalizes the first character of the given string.
std::string EncodeBase32(std::span< const unsigned char > input, bool pad)
Base32 encode.
std::optional< std::vector< unsigned char > > DecodeBase64(std::string_view str)
std::string EncodeBase64(std::span< const unsigned char > input)