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:
@@ -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
|
||||
|
||||
Executable
+29
@@ -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 .."
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
Submodule
+1
Submodule src/i2p/i2pd-src added at 8497a429dc
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user