Skip to content

ThreadI2PAcceptIncoming temporarily bypasses 125 connection ceiling #27843

@Crypt-iQ

Description

@Crypt-iQ
  • The i2p accept code is in ThreadI2PAcceptIncoming, instead of in ThreadSocketHandler like the non-i2p accept code.
  • When accepting non-i2p connections, a connection is accepted in AcceptConnection and if there are more than maxInbound outstanding connections, one is marked for disconnect. The next iteration of the loop in ThreadSocketHandler will call DisconnectNodes and remove it.
  • Since the i2p code resides in its own thread, it's possible for i2p connections to stack up past 125 (I was able to hit 190 outstanding connections when running a local script to connect to my bitcoind i2p address). I think the higher numbered fd's could cause a slight slowdown in select.
  • How many connections it can go past 125 is a race between DisconnectNodes and ThreadI2PAcceptIncoming.
Local test
diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp
index b2e1ae43b..f1f0b2a60 100644
--- a/src/test/i2p_tests.cpp
+++ b/src/test/i2p_tests.cpp
@@ -10,14 +10,52 @@
 #include <test/util/net.h>
 #include <test/util/setup_common.h>
 #include <util/threadinterrupt.h>
+#include <netbase.h>
+#include <protocol.h>
+
+#include <chrono>
+#include <thread>
 
 #include <boost/test/unit_test.hpp>
 
 #include <memory>
 #include <string>
+#include <stdio.h>
 
 BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup)
 
+BOOST_AUTO_TEST_CASE(spam)
+{
+    CThreadInterrupt interrupt;
+
+    in_addr ipv4Addr;
+    ipv4Addr.s_addr = inet_addr("127.0.0.1");
+
+    Proxy p = Proxy(CService(ipv4Addr, 7656), false);
+
+    auto i2p_transient_session = std::make_unique<i2p::sam::Session>(p.proxy, &interrupt);
+
+    const char* pszDest = "tfshhokzifn2g46dc6dxwrwwxwbeo4l5eeepequ52kpvrflm3foq.b32.i2p";
+
+    const std::vector<CService> resolved{Lookup(pszDest, 0, false, 256)};
+
+    CAddress addrConnect = CAddress(resolved[0], NODE_NONE);
+
+    std::vector<std::unique_ptr<Sock>> v;
+    v.reserve(1000);
+
+    for (int i = 0; i < 1000; i++) {
+        i2p::Connection conn;
+        bool proxy_error;
+        auto connected = i2p_transient_session->Connect(addrConnect, conn, proxy_error);
+        if (connected) {
+            v.push_back(std::move(conn.sock));
+        }
+    }
+
+    std::this_thread::sleep_for(std::chrono::milliseconds(50000));
+}
+
 BOOST_AUTO_TEST_CASE(unlimited_recv)
 {
     const auto prev_log_level{LogInstance().LogLevel()};

I then called it from a script:

#!/bin/bash
#
for i in {1..10}
do
	./src/test/test_bitcoin --log_level=all --run_test=i2p_tests/spam &
done

When testing this, I also ran into a reproducible error when simultaneously using bitcoin-cli:

error: timeout on transient error: Could not connect to the server 127.0.0.1:18443 (error code 1 - "EOF reached")

Make sure the bitcoind server is running and that you are connecting to the correct RPC port.

Wireshark showed that a TCP port was reused which led to a RST being sent back to bitcoin-cli. It happened when the i2p sam bridge ran out of memory, but as far as I can tell the rpc server should be totally unaffected by that. This error message also appeared in bitcoind's logs at the same time:
2023-06-09T15:02:58Z connect() to 127.0.0.1:7656 failed: Can't assign requested address (49)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions