diff --git a/src/i2p.cpp b/src/i2p.cpp index c45bcc15d2..9aa1ad0f47 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -12,11 +12,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -115,8 +115,19 @@ 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), - m_control_sock(std::make_unique(INVALID_SOCKET)) + : m_private_key_file{private_key_file}, + m_control_host{control_host}, + m_interrupt{interrupt}, + m_control_sock{std::make_unique(INVALID_SOCKET)}, + m_transient{false} +{ +} + +Session::Session(const CService& control_host, CThreadInterrupt* interrupt) + : m_control_host{control_host}, + m_interrupt{interrupt}, + m_control_sock{std::make_unique(INVALID_SOCKET)}, + m_transient{true} { } @@ -355,29 +366,47 @@ void Session::CreateIfNotCreatedAlready() return; } - Log("Creating SAM session with %s", m_control_host.ToString()); + const auto session_type = m_transient ? "transient" : "persistent"; + const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs + + Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString()); auto sock = Hello(); - const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file); - if (read_ok) { - m_private_key.assign(data.begin(), data.end()); + if (m_transient) { + // The destination (private key) is generated upon session creation and returned + // in the reply in DESTINATION=. + const Reply& reply = SendRequestAndGetReply( + *sock, + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT", session_id)); + + m_private_key = DecodeI2PBase64(reply.Get("DESTINATION")); } else { - GenerateAndSavePrivateKey(*sock); + // Read our persistent destination (private key) from disk or generate + // one and save it to disk. Then use it when creating the session. + 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& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); + + SendRequestAndGetReply(*sock, + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + session_id, + private_key_b64)); } - 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()), I2P_SAM31_PORT); m_session_id = session_id; m_control_sock = std::move(sock); - LogPrintfCategory(BCLog::I2P, "SAM session created: session id=%s, my address=%s\n", - m_session_id, m_my_addr.ToString()); + Log("%s SAM session %s created, my address=%s", + Capitalize(session_type), + m_session_id, + m_my_addr.ToString()); } std::unique_ptr Session::StreamAccept() diff --git a/src/i2p.h b/src/i2p.h index eb0a10103d..ebbcb437da 100644 --- a/src/i2p.h +++ b/src/i2p.h @@ -70,6 +70,19 @@ public: const CService& control_host, CThreadInterrupt* interrupt); + /** + * Construct a transient session which will generate its own I2P private key + * rather than read the one from disk (it will not be saved on disk either and + * will be lost once this object is destroyed). This will not initiate any IO, + * the session will be lazily created later when first used. + * @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 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 @@ -262,6 +275,12 @@ private: * SAM session id. */ std::string m_session_id GUARDED_BY(m_mutex); + + /** + * Whether this is a transient session (the I2P private key will not be + * read or written to disk). + */ + const bool m_transient; }; } // namespace sam diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp index bd9ba4b8f7..80684ac205 100644 --- a/src/test/i2p_tests.cpp +++ b/src/test/i2p_tests.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv) i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", CService{}, &interrupt); { - ASSERT_DEBUG_LOG("Creating SAM session"); + ASSERT_DEBUG_LOG("Creating persistent SAM session"); ASSERT_DEBUG_LOG("too many bytes without a terminator"); i2p::Connection conn;