feat: I2P integration (process-based) + updated icons + Qt UI for I2P address display

This commit is contained in:
2026-06-29 13:45:59 -07:00
parent 6cf30350ea
commit 2aeae07d0b
24 changed files with 1407 additions and 31 deletions
+85 -7
View File
@@ -11,6 +11,7 @@
#include "addrman.h"
#include "ui_interface.h"
#include "onionseed.h"
#include "i2p.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
@@ -494,14 +495,26 @@ CNode* FindNode(const CService& addr)
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
{
// TOR-NATIVE: Reject all non-.onion addresses
// PRIVACY-NATIVE: only anonymous networks are allowed. Accept Tor (.onion)
// and I2P (.i2p / .b32.i2p); reject everything else (clearnet).
std::string addrStr = pszDest ? std::string(pszDest) : addrConnect.ToStringIP();
if (addrStr.find(".onion") == std::string::npos) {
bool fI2P = (addrStr.find(".i2p") != std::string::npos);
if (!fI2P && addrStr.find(".onion") == std::string::npos) {
if (fDebug)
printf("ConnectNode(): REJECTED non-onion address: %s (Tor-native mode)\n", addrStr.c_str());
printf("ConnectNode(): REJECTED non-anonymous address: %s (privacy-native mode)\n", addrStr.c_str());
return nullptr;
}
// For I2P make sure addrConnect carries the destination so the resulting
// CNode is labelled correctly even when we were given a bare pszDest.
if (fI2P && !addrConnect.IsI2P()) {
std::string i2pHost = addrStr;
size_t i2pEnd = i2pHost.find(".i2p");
if (i2pEnd != std::string::npos)
i2pHost = i2pHost.substr(0, i2pEnd + 4);
addrConnect.SetSpecial(i2pHost);
}
if (pszDest == nullptr) {
if (IsLocal(addrConnect))
return nullptr;
@@ -525,8 +538,20 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
pszDest ? 0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0);
// Connect
SOCKET hSocket;
if (pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest, GetDefaultPort()) : ConnectSocket(addrConnect, hSocket))
SOCKET hSocket = INVALID_SOCKET;
bool fConnected;
if (fI2P) {
// Route through the I2P SAM session. Strip any :port suffix; I2P peers
// are reached purely by destination.
std::string i2pDest = addrStr;
size_t i2pEnd = i2pDest.find(".i2p");
if (i2pEnd != std::string::npos)
i2pDest = i2pDest.substr(0, i2pEnd + 4);
fConnected = CI2PSession::GetInstance()->Connect(i2pDest, hSocket);
} else {
fConnected = pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest, GetDefaultPort()) : ConnectSocket(addrConnect, hSocket);
}
if (fConnected)
{
addrman.Attempt(addrConnect);
@@ -561,6 +586,54 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
}
}
// Adopt a connected I2P SAM data socket (from the accept loop in i2p.cpp) as an
// inbound peer. The socket arrives in blocking mode; switch it to non-blocking
// to match the rest of the socket handler, then register the node.
void AddI2PInboundNode(SOCKET hSocket, const CAddress& addr)
{
if (hSocket == INVALID_SOCKET)
return;
if (CNode::IsBanned(addr)) {
printf("I2P inbound from %s dropped (banned)\n", addr.ToString().c_str());
closesocket(hSocket);
return;
}
// Honour the inbound connection limit.
int nInbound = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (pnode->fInbound)
nInbound++;
}
int nMaxInbound = GetArg("-maxconnections", 125) - MAX_OUTBOUND_CONNECTIONS;
if (nInbound >= nMaxInbound) {
printf("I2P inbound from %s dropped (too many inbound)\n", addr.ToString().c_str());
closesocket(hSocket);
return;
}
#ifdef WIN32
u_long nOne = 1;
if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR)
printf("AddI2PInboundNode() : ioctlsocket non-blocking setting failed, error %d\n", WSAGetLastError());
#else
if (fcntl(hSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR)
printf("AddI2PInboundNode() : fcntl non-blocking setting failed, error %d\n", errno);
#endif
printf("accepted I2P connection %s\n", addr.ToString().c_str());
CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef();
pnode->nTimeConnected = GetTime();
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CNode::CloseSocketDisconnect()
{
fDisconnect = true;
@@ -1726,11 +1799,16 @@ bool ThreadHTTPSeedFetch2(void* parg)
// For .onion addresses, the last colon before port is after ".onion"
size_t onionPos = addrStr.find(".onion:");
size_t i2pPos = addrStr.find(".b32.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) {
// Tor-native: skip non-.onion addresses
} else if (i2pPos != std::string::npos) {
port = atoi(addrStr.substr(i2pPos + 9).c_str());
addrStr = addrStr.substr(0, i2pPos + 8); // keep ".b32.i2p"
} else if (addrStr.find(".onion") == std::string::npos &&
addrStr.find(".i2p") == std::string::npos) {
// Privacy-native: skip clearnet addresses
continue;
}