feat: embedded I2P (i2pd) Level 3 — dual-network anonymity

Add a full embedded I2P router (PurpleI2P/i2pd) alongside the existing
embedded Tor, making Triangles a dual-network anonymity cryptocurrency.

Architecture:
- i2pd runs in-process via i2p::api (same pattern as embedded Tor)
- SOCKS proxy (19100) routes outbound .b32.i2p connections
- Server tunnel acts as I2P hidden service (incoming P2P connections)
- SAM bridge (7656) available for future SAM v3 protocol usage
- Auto-generated tunnels.conf with persistent destination keys
- Non-fatal: I2P failure falls back to Tor-only operation

Files:
- src/i2p/i2pd-src/: PurpleI2P/i2pd as git submodule
- src/i2p/i2p_embedded.h/.cpp: CI2PEmbedded router wrapper
- src/i2p/i2pseed.h: .b32.i2p seed node placeholders
- src/i2p/build-libi2pd.sh: static library build script
- CMakeLists.txt: USE_I2P_EMBEDDED option (default OFF)
- src/init.cpp: I2P startup/shutdown wiring
- src/net.cpp: allow .b32.i2p in ConnectNode + seed parsing
- src/netbase.cpp: I2P SOCKS routing in ConnectSocketByName,
  fixed .b32.i2p address parsing (was broken .oc.b32.i2p only)

Build: cmake -DUSE_I2P_EMBEDDED=ON
Test: verified daemon starts, creates .b32.i2p destination,
      builds tunnels, connects to I2P network
This commit is contained in:
Krystie
2026-06-27 16:32:42 -07:00
parent 0c6a2223cb
commit cf2ff6768d
11 changed files with 601 additions and 15 deletions
+44
View File
@@ -86,6 +86,7 @@ set(CORE_SOURCES
tor/onion_v3.cpp
tor/tor_process.cpp
tor/tor_embedded.cpp
i2p/i2p_embedded.cpp
)
# Scrypt assembly — platform-specific
@@ -113,6 +114,7 @@ target_include_directories(triangles_common PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/json"
"${CMAKE_CURRENT_SOURCE_DIR}/tor"
"${CMAKE_CURRENT_SOURCE_DIR}/i2p"
"${CMAKE_BINARY_DIR}/generated" # for build.h
)
@@ -212,6 +214,48 @@ if(USE_TOR_EMBEDDED)
endif()
endif()
# Optional: Embedded I2P (i2pd)
if(USE_I2P_EMBEDDED)
if(I2P_SOURCE_ROOT STREQUAL "")
set(I2P_SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/i2p/i2pd-src")
endif()
if(NOT EXISTS "${I2P_SOURCE_ROOT}/libi2pd/Crypto.h")
message(FATAL_ERROR
"USE_I2P_EMBEDDED=ON but i2pd source not found at ${I2P_SOURCE_ROOT}.\n"
"Run: git submodule update --init --recursive\n"
"Or set -DI2P_SOURCE_ROOT=/path/to/i2pd")
endif()
target_compile_definitions(triangles_common PUBLIC ENABLE_I2P_EMBEDDED)
target_include_directories(triangles_common PUBLIC
"${I2P_SOURCE_ROOT}"
"${I2P_SOURCE_ROOT}/libi2pd"
"${I2P_SOURCE_ROOT}/libi2pd_client"
"${I2P_SOURCE_ROOT}/i18n"
)
# i2pd builds as two static libraries: libi2pd.a (core router) and
# libi2pd_client.a (SAM, SOCKS, tunnels, client context). Both are needed.
# We link them with --start-group to resolve circular deps with Boost/SSL.
if(NOT APPLE)
target_link_libraries(triangles_common PUBLIC
-Wl,--allow-multiple-definition
-Wl,--start-group
)
endif()
target_link_libraries(triangles_common PUBLIC
"${I2P_SOURCE_ROOT}/libi2pd.a"
"${I2P_SOURCE_ROOT}/libi2pdclient.a"
"${I2P_SOURCE_ROOT}/libi2pdlang.a"
Boost::program_options Boost::thread Boost::chrono
OpenSSL::SSL OpenSSL::Crypto
ZLIB::ZLIB
)
if(NOT APPLE)
target_link_libraries(triangles_common PUBLIC
-Wl,--end-group
)
endif()
endif()
# Platform-specific libraries
if(WIN32)
target_link_libraries(triangles_common PUBLIC
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
I2PD_SRC_DIR="${I2PD_SRC_DIR:-$ROOT_DIR/i2pd-src}"
if [[ ! -d "$I2PD_SRC_DIR" ]]; then
echo "i2pd source tree not found at: $I2PD_SRC_DIR" >&2
exit 1
fi
cd "$I2PD_SRC_DIR"
echo "Building libi2pd static libraries from: $I2PD_SRC_DIR"
NPROC_VAL="${NPROC:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}"
# i2pd uses a hand-written Makefile system. We build only the static library
# targets (libi2pd.a, libi2pdclient.a, libi2pdlang.a), NOT the standalone
# i2pd daemon binary, which pulls in HTTPServer/I2PControl deps we don't need
# and can OOM on memory-constrained build machines.
make -j"$NPROC_VAL" USE_STATIC=no libi2pd.a libi2pdclient.a libi2pdlang.a
echo
echo "Build finished. Static libraries:"
ls -lh libi2pd*.a
echo
echo "Suggested next step for Triangles:"
echo " cmake -DUSE_I2P_EMBEDDED=ON -DI2P_SOURCE_ROOT=src/i2p/i2pd-src .."
+325
View File
@@ -0,0 +1,325 @@
// Copyright (c) 2025-2026 Triangles developers
// Embedded I2P (i2pd) integration - runs an I2P router in-process
// Distributed under the MIT/X11 software license
//
// BUILD REQUIREMENT: Link against libi2pd.a + libi2pd_client.a built from
// the PurpleI2P/i2pd source tree (src/i2p/i2pd-src).
//
// This file compiles in two modes:
// 1. ENABLE_I2P_EMBEDDED defined: full embedded i2pd via i2p::api
// 2. ENABLE_I2P_EMBEDDED not defined: stubs that report I2P unavailable
#include "i2p_embedded.h"
#include "../util.h"
#include "../net.h"
#include <filesystem>
#include <thread>
#include <fstream>
#include <cstring>
#include <chrono>
#include <ctime>
#include <vector>
#include <string>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
namespace fs = std::filesystem;
// Singleton
CI2PEmbedded* CI2PEmbedded::instance = nullptr;
CI2PEmbedded* CI2PEmbedded::GetInstance()
{
if (!instance)
instance = new CI2PEmbedded();
return instance;
}
CI2PEmbedded::CI2PEmbedded()
: running(false)
, socksPort(19100)
, samPort(7656)
, serverPort(0)
{
}
CI2PEmbedded::~CI2PEmbedded()
{
Stop();
}
std::string CI2PEmbedded::GetSocksProxy() const
{
return "127.0.0.1:" + std::to_string(socksPort);
}
#ifdef ENABLE_I2P_EMBEDDED
// ========================================================================
// Embedded mode: i2pd runs in-process via libi2pd / i2p::api
// ========================================================================
// i2pd C++ API
#include "Config.h"
#include "Log.h"
#include "FS.h"
#include "Crypto.h"
#include "NetDb.hpp"
#include "Transports.h"
#include "Tunnel.h"
#include "RouterContext.h"
#include "Streaming.h"
#include "Destination.h"
#include "ClientContext.h"
#include "I2PTunnel.h"
#include "api.h"
static std::unique_ptr<i2p::client::I2PServerTunnel> g_i2pServerTunnel;
static std::shared_ptr<i2p::client::ClientDestination> g_i2pServerDestination;
bool CI2PEmbedded::Start(int socks, int sam, int server)
{
if (running.load()) return true;
lastError.clear();
socksPort = socks;
samPort = sam;
serverPort = server;
i2pHostname.clear();
// Prepare i2pd data directory under the wallet's data dir
i2pDataDir = (::GetDataDir() / "i2p_data").string();
fs::create_directories(i2pDataDir);
fs::permissions(i2pDataDir, fs::perms::owner_all, fs::perm_options::replace);
printf("Embedded I2P: starting i2pd router...\n");
// Write an i2pd.conf configuration file that enables SAM + SOCKS proxy.
// i2pd's config system reads from a file; programmatic option setting is
// fragile across i2pd versions. Writing a minimal conf is robust.
{
fs::path confPath = fs::path(i2pDataDir) / "i2pd.conf";
std::ofstream conf(confPath.string());
if (!conf.is_open()) {
lastError = "Failed to write i2pd.conf";
return false;
}
conf << "# Auto-generated by Triangles embedded I2P\n";
conf << "datadir = " << i2pDataDir << "\n";
conf << "loglevel = info\n";
conf << "\n";
// SOCKS proxy for outbound .i2p connections (P2P transport)
conf << "[socksproxy]\n";
conf << "enabled = true\n";
conf << "address = 127.0.0.1\n";
conf << "port = " << socksPort << "\n";
conf << "keys = socks-proxy.dat\n";
conf << "\n";
// SAM bridge (for future SAM v3 API usage)
conf << "[sam]\n";
conf << "enabled = true\n";
conf << "address = 127.0.0.1\n";
conf << "port = " << samPort << "\n";
conf << "\n";
// Disable HTTP webconsole (not needed for embedded use)
conf << "[http]\n";
conf << "enabled = false\n";
conf << "\n";
// Disable I2P control protocol
conf << "[i2pcontrol]\n";
conf << "enabled = false\n";
conf << "\n";
// Disable BOB
conf << "[bob]\n";
conf << "enabled = false\n";
conf << "\n";
conf.close();
}
// Write tunnels.conf BEFORE Start() — ClientContext::Start() reads this
// file to create server/client tunnels. The server tunnel is the I2P
// equivalent of a Tor hidden service: it forwards inbound I2P connections
// to the Triangles P2P listen port.
if (serverPort > 0) {
fs::path tunnelConfPath = fs::path(i2pDataDir) / "tunnels.conf";
std::ofstream tunnelConf(tunnelConfPath.string());
if (tunnelConf.is_open()) {
tunnelConf << "# Auto-generated by Triangles embedded I2P\n";
tunnelConf << "[triangles-p2p]\n";
tunnelConf << "type = server\n";
tunnelConf << "host = 127.0.0.1\n";
tunnelConf << "port = " << serverPort << "\n";
tunnelConf << "keys = triangles-p2p-keys.dat\n";
tunnelConf << "inbound.length = 3\n";
tunnelConf << "outbound.length = 3\n";
tunnelConf << "inbound.quantity = 5\n";
tunnelConf << "outbound.quantity = 5\n";
tunnelConf.close();
printf("Embedded I2P: server tunnel configured on port %d\n", serverPort);
}
}
// Build argv for i2pd initialization. Pass --datadir and --conf on the
// command line (not just in the conf file) because i2pd's ParseCmdline
// runs BEFORE ParseConfig, and DetectDataDir needs the datadir early.
std::vector<std::string> argvStrings;
argvStrings.push_back("i2pd");
argvStrings.push_back("--datadir");
argvStrings.push_back(i2pDataDir);
argvStrings.push_back("--conf");
argvStrings.push_back((fs::path(i2pDataDir) / "i2pd.conf").string());
std::vector<char*> argvPtrs;
for (auto& s : argvStrings)
argvPtrs.push_back(&s[0]);
argvPtrs.push_back(nullptr);
try {
// Initialize i2pd: config parse, filesystem, crypto, router context
i2p::api::InitI2P((int)(argvPtrs.size() - 1), argvPtrs.data(), "triangles-i2pd");
// Start the I2P router: netdb, transports, tunnels, router context
// Redirect i2pd logs to our stdout/stderr
auto logStream = std::make_shared<std::ostream>(std::cout.rdbuf());
i2p::api::StartI2P(logStream);
printf("Embedded I2P: router started, starting client services...\n");
// Start the client context — this initializes SAM bridge, SOCKS proxy,
// and tunnels based on config. The client context reads the conf we
// wrote above to determine which services to start.
i2p::client::context.Start();
running.store(true);
printf("Embedded I2P: SOCKS proxy at 127.0.0.1:%d, SAM at 127.0.0.1:%d\n",
socksPort, samPort);
// Wait for i2pd's SOCKS proxy to become available (up to 120s — I2P
// bootstrap is slower than Tor due to floodfill lookup and tunnel build)
printf("Embedded I2P: waiting for SOCKS proxy to become available...\n");
for (int i = 0; i < 120; i++) {
MilliSleep(1000);
if (fShutdown) {
Stop();
return false;
}
#ifdef WIN32
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock != INVALID_SOCKET) {
#else
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock >= 0) {
#endif
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(socksPort);
bool up = (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0);
#ifdef WIN32
closesocket(sock);
#else
close(sock);
#endif
if (up) {
printf("Embedded I2P: SOCKS proxy ready on port %d (took %ds)\n",
socksPort, i + 1);
return true;
}
}
if (i > 0 && i % 30 == 0) {
printf("Embedded I2P: still bootstrapping (%ds elapsed)...\n", i);
}
}
// SOCKS not ready after 120s — I2P may still be building tunnels.
// We return true anyway; connections will retry once tunnels are up.
printf("Embedded I2P: SOCKS proxy not ready after 120s (I2P bootstrap in progress)\n");
printf(" Outbound .i2p connections will retry automatically.\n");
return true;
} catch (const std::exception& e) {
lastError = std::string("i2pd initialization failed: ") + e.what();
printf("ERROR: Embedded I2P startup failed: %s\n", e.what());
return false;
}
}
void CI2PEmbedded::Stop()
{
if (!running.load()) return;
printf("Requesting embedded I2P shutdown...\n");
try {
// Stop client context (SAM, SOCKS, tunnels)
i2p::client::context.Stop();
// Stop the router
i2p::api::StopI2P();
// Terminate crypto
i2p::api::TerminateI2P();
} catch (const std::exception& e) {
printf("WARNING: error during I2P shutdown: %s\n", e.what());
}
running.store(false);
}
#else // !ENABLE_I2P_EMBEDDED
// ========================================================================
// Fallback stubs: embedded I2P not compiled in
// ========================================================================
bool CI2PEmbedded::Start(int socks, int sam, int server)
{
printf("Embedded I2P not compiled in (ENABLE_I2P_EMBEDDED not defined).\n");
socksPort = socks;
samPort = sam;
serverPort = server;
i2pDataDir = (::GetDataDir() / "i2p_data").string();
lastError = "I2P support not compiled in. Build with -DUSE_I2P_EMBEDDED=ON";
return false;
}
void CI2PEmbedded::Stop()
{
running.store(false);
}
#endif // ENABLE_I2P_EMBEDDED
// ========================================================================
// Global hooks (called from init.cpp)
// ========================================================================
bool StartEmbeddedI2P()
{
bool enableI2P = GetBoolArg("-i2p", true);
if (!enableI2P) {
printf("I2P disabled by -i2p=0 flag\n");
return false;
}
int socksPort = GetArg("-i2psocks", 19100);
int samPort = GetArg("-i2psam", 7656);
int serverPort = GetArg("-i2phsport", GetListenPort());
return CI2PEmbedded::GetInstance()->Start(socksPort, samPort, serverPort);
}
void StopEmbeddedI2P()
{
CI2PEmbedded::GetInstance()->Stop();
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2025-2026 Triangles developers
// Embedded I2P (i2pd) integration - runs an I2P router in-process
// Distributed under the MIT/X11 software license
#ifndef TRIANGLES_I2P_EMBEDDED_H
#define TRIANGLES_I2P_EMBEDDED_H
#include <string>
#include <atomic>
// Embedded I2P router state
class CI2PEmbedded
{
private:
static CI2PEmbedded* instance;
std::atomic<bool> running;
int socksPort; // i2pd SOCKS proxy port (for outbound .i2p connections)
int samPort; // i2pd SAM bridge port (for SAM v3 protocol)
int serverPort; // Triangles P2P listen port (for incoming I2P connections)
std::string i2pDataDir; // i2pd data directory (under wallet datadir)
std::string i2pHostname; // Our .b32.i2p address (available after router startup)
std::string lastError;
public:
static CI2PEmbedded* GetInstance();
CI2PEmbedded();
~CI2PEmbedded();
// Start embedded i2pd router (blocks calling thread briefly during init)
bool Start(int socksPort = 19100, int samPort = 7656, int serverPort = 0);
// Request i2pd to shut down
void Stop();
// Check if i2pd is running
bool IsRunning() const { return running.load(); }
void SetRunning(bool value) { running.store(value); }
// Get the SOCKS5 proxy address for outbound .i2p connections
std::string GetSocksProxy() const;
int GetSocksPort() const { return socksPort; }
int GetSamPort() const { return samPort; }
int GetServerPort() const { return serverPort; }
const std::string& GetDataDir() const { return i2pDataDir; }
// Get our .b32.i2p destination address
std::string GetI2PAddress() const { return i2pHostname; }
std::string GetStartupError() const { return lastError; }
void SetStartupError(const std::string& value) { lastError = value; }
};
// Global init/shutdown hooks (called from init.cpp)
bool StartEmbeddedI2P();
void StopEmbeddedI2P();
#endif // TRIANGLES_I2P_EMBEDDED_H
+1
Submodule src/i2p/i2pd-src added at 8497a429dc
+27
View File
@@ -0,0 +1,27 @@
#ifndef TRIANGLES_I2PSEED_H
#define TRIANGLES_I2PSEED_H
// Hardcoded I2P seed nodes for initial peer discovery.
// These are .b32.i2p addresses (Destination hashes).
// Nodes must run i2pd with a server tunnel forwarding to the Triangles P2P port.
//
// NOTE: .b32.i2p addresses are derived from the destination's public key.
// They are generated when the node first creates its I2P tunnel keys.
// Replace these placeholders with actual seed node addresses once deployed.
//
// Dynamic seeds will also be available at:
// https://seeds.cryptographic-triangles.org/i2p-seeds.txt
static const char *strMainNetI2PSeed[][1] = {
// DNS2 - primary bootstrap server
// Generate .b32.i2p by: i2pd --datadir=/root/.triangles/i2p_data
// then read: i2p_data/triangles-p2p-keys.dat → b32 address
// Placeholder until seed nodes are deployed:
// {"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.b32.i2p"},
{nullptr}
};
static const char *strTestNetI2PSeed[][1] = {
{nullptr}
};
#endif
+48 -1
View File
@@ -19,6 +19,7 @@
#include "tor/tor_embedded.h"
#include "tor/onion_v3.h"
#include "tor/tor_process.h"
#include "i2p/i2p_embedded.h"
#ifdef ENABLE_ZMQ
#include "zmqpublishnotifier.h"
#endif
@@ -341,6 +342,7 @@ void Shutdown(void* parg)
// NOW safe to destroy Tor state - all threads have stopped
ShutdownTorV3();
StopEmbeddedTor();
StopEmbeddedI2P();
#ifdef ENABLE_ZMQ
if (pzmqNotifier)
@@ -533,7 +535,11 @@ std::string HelpMessage()
" -notor " + _("Disable Tor - run in clearnet-only mode (no .onion connectivity)") + "\n" +
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n"
" -i2p " + _("Enable embedded I2P router for .b32.i2p connectivity (default: 1)") + "\n"
" -i2psocks=<port> " + _("Set embedded I2P SOCKS proxy port (default: 19100)") + "\n"
" -i2psam=<port> " + _("Set embedded I2P SAM bridge port (default: 7656)") + "\n"
" -i2phsport=<port> " + _("Set I2P server tunnel forward port (default: wallet listen port)") + "\n" +
//" -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n" +
" -port=<port> " + _("Listen for connections on <port> (default: 24112 or testnet: 24111)") + "\n" +
" -maxconnections=<n> " + _("Maintain at most <n> connections to peers (default: 125)") + "\n" +
@@ -1546,6 +1552,47 @@ bool AppInit2()
return InitError(strprintf(_("Tor failed to start. Triangles requires Tor to operate.\n\nDetails: %s"), torError.c_str()));
}
// ════════════════════════════════════════════════════════════════
// Embedded I2P (i2pd) startup
//
// I2P runs as a co-equal anonymity network alongside Tor. When Tor
// starts successfully (tor-native mode), I2P provides an alternative
// anonymous transport via .b32.i2p destinations. When Tor is disabled
// (-notor recovery mode), I2P is still started to maintain anonymity.
//
// I2P's SOCKS proxy (default 19100) handles outbound .i2p connections.
// A server tunnel forwards incoming I2P connections to the P2P port.
// ════════════════════════════════════════════════════════════════
if (torStarted || GetBoolArg("-notor", false)) {
uiInterface.InitMessage(_("Starting embedded I2P router..."));
int64_t nI2PStart = GetTimeMillis();
bool i2pStarted = StartEmbeddedI2P();
StartupPerfLog("i2p_start", GetTimeMillis() - nI2PStart,
strprintf("started=%d", i2pStarted));
if (i2pStarted) {
int i2pSocksPort = CI2PEmbedded::GetInstance()->GetSocksPort();
CService i2pProxyAddr("127.0.0.1", i2pSocksPort);
// Route I2P traffic through i2pd's SOCKS proxy
SetProxy(NET_I2P, i2pProxyAddr, 5);
SetReachable(NET_I2P, true);
printf("I2P-NATIVE MODE: I2P router running\n");
printf(" SOCKS proxy at 127.0.0.1:%d for .b32.i2p connections\n",
i2pSocksPort);
printf(" Dual-network anonymity: Tor (.onion) + I2P (.b32.i2p)\n");
} else {
// I2P failure is non-fatal — Tor-only operation continues.
// The daemon still works with .onion peers.
std::string i2pError = CI2PEmbedded::GetInstance()->GetStartupError();
printf("WARNING: Embedded I2P did not start. Running Tor-only.\n");
if (!i2pError.empty())
printf(" I2P error: %s\n", i2pError.c_str());
SetReachable(NET_I2P, false);
}
}
// Initialize Tor V3 identity (Ed25519 keys, onion address)
uiInterface.InitMessage(_("Initializing Tor V3 identity..."));
printf("Initializing Tor V3 onion identity...\n");
+12 -5
View File
@@ -496,11 +496,13 @@ CNode* FindNode(const CService& addr)
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
{
// TOR-NATIVE: Reject all non-.onion addresses
// TOR+I2P NATIVE: Reject all clearnet (non-.onion, non-.b32.i2p) addresses
std::string addrStr = pszDest ? std::string(pszDest) : addrConnect.ToStringIP();
if (addrStr.find(".onion") == std::string::npos) {
bool isOnion = (addrStr.find(".onion") != std::string::npos);
bool isI2P = (addrStr.find(".i2p") != std::string::npos);
if (!isOnion && !isI2P) {
if (fDebug)
printf("ConnectNode(): REJECTED non-onion address: %s (Tor-native mode)\n", addrStr.c_str());
printf("ConnectNode(): REJECTED clearnet address: %s (Tor/I2P native mode)\n", addrStr.c_str());
return nullptr;
}
@@ -1836,11 +1838,16 @@ bool ThreadHTTPSeedFetch2(void* parg)
int port = GetDefaultPort();
size_t onionPos = addrStr.find(".onion:");
size_t i2pPos = addrStr.find(".i2p:");
if (onionPos != std::string::npos) {
port = atoi(addrStr.substr(onionPos + 7).c_str());
addrStr = addrStr.substr(0, onionPos + 6); // keep ".onion"
} else if (addrStr.find(".onion") == std::string::npos) {
return; // Tor-native: skip non-.onion addresses
} else if (i2pPos != std::string::npos) {
port = atoi(addrStr.substr(i2pPos + 5).c_str());
// keep the ".i2p" suffix
} else if (addrStr.find(".onion") == std::string::npos &&
addrStr.find(".i2p") == std::string::npos) {
return; // Tor/I2P-native: skip clearnet addresses
}
if (port <= 0 || port > 65535)
port = GetDefaultPort();
+48 -9
View File
@@ -591,6 +591,33 @@ bool ConnectSocketByName(CService &addr, SOCKET& hSocketRet, const char *pszDest
SOCKET hSocket = INVALID_SOCKET;
// I2P routing: .b32.i2p destinations go through i2pd's SOCKS proxy, not
// the Tor name proxy. This is the key routing decision for dual-network
// anonymity — Tor handles .onion, i2pd handles .b32.i2p.
bool isI2PDest = (strDest.size() > 7 &&
strDest.substr(strDest.size() - 7, 7) == ".b32.i2p");
if (isI2PDest) {
// Route through the I2P SOCKS proxy
proxyType i2pProxy;
if (GetProxy(NET_I2P, i2pProxy)) {
addr = CService("0.0.0.0:0");
printf("ConnectSocketByName(): routing .b32.i2p via I2P SOCKS proxy\n");
if (!ConnectSocketDirectly(i2pProxy.first, hSocket, nTimeout))
return false;
// i2pd's SOCKS proxy accepts .b32.i2p domain names via SOCKS5 ATYP=domain
if (!Socks5(strDest, port, hSocket)) {
printf("ConnectSocketByName(): I2P SOCKS5 handshake failed\n");
return false;
}
printf("ConnectSocketByName(): connected via I2P SOCKS5\n");
hSocketRet = hSocket;
return true;
}
// No I2P proxy configured — fall through to nameproxy (will likely fail)
printf("ConnectSocketByName(): WARNING - .b32.i2p dest but no I2P proxy set\n");
}
proxyType nameproxy;
GetNameProxy(nameproxy);
@@ -672,14 +699,26 @@ bool CNetAddr::SetSpecial(const std::string &strName)
m_is_tor_v3 = false;
return true;
}
if (strName.size()>11 && strName.substr(strName.size() - 11, 11) == ".oc.b32.i2p") {
std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 11).c_str());
if (vchAddr.size() != 16-sizeof(pchGarliCat))
return false;
memcpy(ip, pchOnionCat, sizeof(pchGarliCat));
for (unsigned int i=0; i<16-sizeof(pchGarliCat); i++)
ip[i + sizeof(pchGarliCat)] = vchAddr[i];
return true;
// Standard I2P b32 address: <52 base32 chars>.b32.i2p
// (SHA-256 hash of destination key, base32-encoded)
if (strName.size()>7 && strName.substr(strName.size() - 7, 7) == ".b32.i2p") {
std::string b32Part = strName.substr(0, strName.size() - 7);
std::vector<unsigned char> vchAddr = DecodeBase32(b32Part.c_str());
if (vchAddr.size() == 32) {
// Standard 32-byte I2P destination hash
memcpy(ip, pchGarliCat, sizeof(pchGarliCat));
// Store as many bytes as fit (16 - prefix_size)
for (unsigned int i = 0; i < 16 - sizeof(pchGarliCat) && i < vchAddr.size(); i++)
ip[i + sizeof(pchGarliCat)] = vchAddr[i];
return true;
}
// Also handle the legacy .oc.b32.i2p format (10 bytes)
if (vchAddr.size() == 16 - sizeof(pchGarliCat)) {
memcpy(ip, pchGarliCat, sizeof(pchGarliCat));
for (unsigned int i = 0; i < 16 - sizeof(pchGarliCat); i++)
ip[i + sizeof(pchGarliCat)] = vchAddr[i];
return true;
}
}
return false;
}
@@ -910,7 +949,7 @@ std::string CNetAddr::ToStringIP() const
if (IsTor())
return EncodeBase32(&ip[6], 10) + ".onion";
if (IsI2P())
return EncodeBase32(&ip[6], 10) + ".oc.b32.i2p";
return EncodeBase32(&ip[6], 10) + ".b32.i2p";
CService serv(*this, 0);
#ifdef USE_IPV6
struct sockaddr_storage sockaddr;