Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2020-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <chainparams.h>6#include <common/args.h>7#include <compat/compat.h>8#include <compat/endian.h>9#include <crypto/sha256.h>10#include <i2p.h>11#include <netaddress.h>12#include <netbase.h>13#include <random.h>14#include <script/parsing.h>15#include <sync.h>16#include <tinyformat.h>17#include <util/fs.h>18#include <util/log.h>19#include <util/readwritefile.h>20#include <util/sock.h>21#include <util/strencodings.h>22#include <util/threadinterrupt.h>24#include <chrono>25#include <memory>26#include <ranges>27#include <stdexcept>28#include <string>30using util::Split;32namespace i2p {34/**35* Swap Standard Base64 <-> I2P Base64.36* Standard Base64 uses `+` and `/` as last two characters of its alphabet.37* I2P Base64 uses `-` and `~` respectively.38* So it is easy to detect in which one is the input and convert to the other.39* @param[in] from Input to convert.40* @return converted `from`41*/42static std::string SwapBase64(const std::string& from)43{44std::string to;45to.resize(from.size());46for (size_t i = 0; i < from.size(); ++i) {47switch (from[i]) {48case '-':49to[i] = '+';50break;51case '~':52to[i] = '/';53break;54case '+':55to[i] = '-';56break;57case '/':58to[i] = '~';59break;60default:61to[i] = from[i];62break;63}64}65return to;66}68/**69* Decode an I2P-style Base64 string.70* @param[in] i2p_b64 I2P-style Base64 string.71* @return decoded `i2p_b64`72* @throw std::runtime_error if decoding fails73*/74static Binary DecodeI2PBase64(const std::string& i2p_b64)75{76const std::string& std_b64 = SwapBase64(i2p_b64);77auto decoded = DecodeBase64(std_b64);78if (!decoded) {79throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));80}81return std::move(*decoded);82}84/**85* Derive the .b32.i2p address of an I2P destination (binary).86* @param[in] dest I2P destination.87* @return the address that corresponds to `dest`88* @throw std::runtime_error if conversion fails89*/90static CNetAddr DestBinToAddr(const Binary& dest)91{92CSHA256 hasher;93hasher.Write(dest.data(), dest.size());94unsigned char hash[CSHA256::OUTPUT_SIZE];95hasher.Finalize(hash);97CNetAddr addr;98const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p";99if (!addr.SetSpecial(addr_str)) {100throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));101}103return addr;104}106/**107* Derive the .b32.i2p address of an I2P destination (I2P-style Base64).108* @param[in] dest I2P destination.109* @return the address that corresponds to `dest`110* @throw std::runtime_error if conversion fails111*/112static CNetAddr DestB64ToAddr(const std::string& dest)113{114const Binary& decoded = DecodeI2PBase64(dest);115return DestBinToAddr(decoded);116}118namespace sam {120Session::Session(const fs::path& private_key_file,121const Proxy& control_host,122std::shared_ptr<CThreadInterrupt> interrupt)123: m_private_key_file{private_key_file},124m_control_host{control_host},125m_interrupt{interrupt},126m_transient{false}127{128}130Session::Session(const Proxy& control_host, std::shared_ptr<CThreadInterrupt> interrupt)131: m_control_host{control_host},132m_interrupt{interrupt},133m_transient{true}134{135}137Session::~Session()138{139LOCK(m_mutex);140Disconnect();141}143bool Session::Listen(Connection& conn)144{145try {146LOCK(m_mutex);147CreateIfNotCreatedAlready();148conn.me = m_my_addr;149conn.sock = StreamAccept();150return true;151} catch (const std::runtime_error& e) {152LogError("Couldn't listen: %s\n", e.what());153CheckControlSock();154}155return false;156}158bool Session::Accept(Connection& conn)159{160AssertLockNotHeld(m_mutex);162std::string errmsg;163bool disconnect{false};165while (!m_interrupt->interrupted()) {166Sock::Event occurred;167if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RecvEvent, &occurred)) {168errmsg = "wait on socket failed";169break;170}172if (occurred == 0) {173// Timeout, no incoming connections or errors within MAX_WAIT_FOR_IO.174continue;175}177std::string peer_dest;178try {179peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);180} catch (const std::runtime_error& e) {181errmsg = e.what();182break;183}185CNetAddr peer_addr;186try {187peer_addr = DestB64ToAddr(peer_dest);188} catch (const std::runtime_error& e) {189// The I2P router is expected to send the Base64 of the connecting peer,190// but it may happen that something like this is sent instead:191// STREAM STATUS RESULT=I2P_ERROR MESSAGE="Session was closed"192// In that case consider the session damaged and close it right away,193// even if the control socket is alive.194if (peer_dest.find("RESULT=I2P_ERROR") != std::string::npos) {195errmsg = strprintf("unexpected reply that hints the session is unusable: %s", peer_dest);196disconnect = true;197} else {198errmsg = e.what();199}200break;201}203conn.peer = CService(peer_addr, I2P_SAM31_PORT);205return true;206}208if (m_interrupt->interrupted()) {209LogDebug(BCLog::I2P, "Accept was interrupted\n");210} else {211LogDebug(BCLog::I2P, "Error accepting%s: %s\n", disconnect ? " (will close the session)" : "", errmsg);212}213if (disconnect) {214LOCK(m_mutex);215Disconnect();216} else {217CheckControlSock();218}219return false;220}222bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)223{224// Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy225// when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT.226if (to.GetPort() != I2P_SAM31_PORT) {227LogDebug(BCLog::I2P, "Error connecting to %s, connection refused due to arbitrary port %s\n", to.ToStringAddrPort(), to.GetPort());228proxy_error = false;229return false;230}232proxy_error = true;234std::string session_id;235std::unique_ptr<Sock> sock;236conn.peer = to;238try {239{240LOCK(m_mutex);241CreateIfNotCreatedAlready();242session_id = m_session_id;243conn.me = m_my_addr;244sock = Hello();245}247const Reply& lookup_reply =248SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringAddr()));250const std::string& dest = lookup_reply.Get("VALUE");