net: use the proxy if overriden when doing v2->v1 reconnections
What changed, and why it matters
This commit fixes a bug in Bitcoin Core's networking code. When a user or operator had configured a specific proxy (a privacy-routing middleman) to be used for a particular outgoing connection, that proxy was correctly used on the first connection attempt. But if the modern v2 transport protocol handshake failed and Bitcoin Core automatically fell back to the older v1 protocol, the fallback connection ignored the specified proxy and used the global/default proxy instead. The fix stores the requested proxy override inside the peer object so the fallback reconnection remembers to use it.
Treat as a low-to-moderate privacy fix. Users relying on per-connection proxy overrides (e.g., for I2P/CJDNS/isolated outbound peers) should upgrade so that v2->v1 fallbacks do not leak connections through the wrong proxy. No emergency action is indicated; no memory corruption or remote code execution is present.
Security signals we found
Privacy/routing-policy bypass: per-connection proxy override ignored on fallback reconnection
Network-layer behavior inconsistency between initial connection and v2->v1 reconnection
Fix is targeted and state-preserving: stores override in CNode and reconnection queue
Evidence from the diff
OpenNetworkConnection() accepts an optional proxy_override argument that should be used instead of the global proxy for a specific outbound connection. The initial ConnectNode() path honored it, but the v2->v1 reconnection path did not: when CConnman::DisconnectNodes() queued a ReconnectionInfo for a v1 retry, it omitted the proxy override, and PerformReconnections() called ConnectNode() without passing it back. The patch adds proxy_override to CNodeOptions, stores it as CNode::m_proxy_override, copies it into ReconnectionInfo, and forwards it to ConnectNode() during reconnection. This is a correctness/privacy fix, not a memory-safety bug.
Changed components
src/net.cppsrc/net.hCConnman::ConnectNodeCConnman::DisconnectNodesCConnman::PerformReconnectionsCNodeOptionsCNode::m_proxy_overrideReconnectionInfoInspect captured patch +11 / −1
diff --git a/src/net.cpp b/src/net.cpp
index f8149ef3..7edb8eac 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -539,6 +539,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect,
network_id,
CNodeOptions{
.permission_flags = permission_flags,
+ .proxy_override = proxy_override,
.i2p_sam_session = std::move(i2p_transient_session),
.recv_flood_size = nReceiveFloodSize,
.use_v2transport = use_v2transport,
@@ -1956,6 +1957,7 @@ void CConnman::DisconnectNodes()
// and we don't want to hold up the socket handler thread for that long.
if (network_active && pnode->m_transport->ShouldReconnectV1()) {
reconnections_to_add.push_back({
+ .proxy_override = pnode->m_proxy_override,
.addr_connect = pnode->addr,
.grant = std::move(pnode->grantOutbound),
.destination = pnode->m_dest,
@@ -4028,6 +4030,7 @@ CNode::CNode(NodeId idIn,
m_permission_flags{node_opts.permission_flags},
m_sock{sock},
m_connected{NodeClock::now()},
+ m_proxy_override{std::move(node_opts.proxy_override)},
addr{addrIn},
addrBind{addrBindIn},
m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn},
@@ -4214,7 +4217,8 @@ void CConnman::PerformReconnections()
std::move(item.grant),
item.destination.empty() ? nullptr : item.destination.c_str(),
item.conn_type,
- item.use_v2transport);
+ item.use_v2transport,
+ item.proxy_override);
}
}
diff --git a/src/net.h b/src/net.h
index d0171c53..fe8322d1 100644
--- a/src/net.h
+++ b/src/net.h
@@ -669,6 +669,7 @@ public:
struct CNodeOptions
{
NetPermissionFlags permission_flags = NetPermissionFlags::None;
+ std::optional<Proxy> proxy_override = {};
std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
bool prefer_evict = false;
size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000};
@@ -711,6 +712,10 @@ public:
std::atomic<NodeClock::time_point> m_last_recv{NodeClock::epoch};
//! Unix epoch time at peer connection
const NodeClock::time_point m_connected;
+
+ //! Proxy to use regardless of global proxy settings if reconnecting to this node.
+ const std::optional<Proxy> m_proxy_override;
+
// Address of this peer
const CAddress addr;
// Bind address of our side of the connection
@@ -1796,6 +1801,7 @@ private:
/** Struct for entries in m_reconnections. */
struct ReconnectionInfo
{
+ std::optional<Proxy> proxy_override;
CAddress addr_connect;
CountingSemaphoreGrant<> grant;
std::string destination;
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.