i2p: pass base32 address directly to stream connect #36570

pull jpk68 wants to merge 1 commits into bitcoin:master from jpk68:i2p-connect changing 1 files +1 −6
  1. jpk68 commented at 12:14 AM on October 19, 2026: contributor

    As the SAM protocol accepts a .b32.i2p address as the DESTINATION parameter for the STREAM CONNECT command, the NAMING LOOKUP round trip can be skipped on every outbound connection.

    This should ostensibly make failure handling more accurate in certain situations as well, since a peer that cannot be resolved now returns CANT_REACH_PEER/TIMEOUT rather than there being an empty VALUE= key in the lookup reply. A missing VALUE= is a proxy error, so the attempt isn't recorded in AddrMan.

    Router implementation versions that support this (Java I2P 0.9.48, i2pd 2.38.0) are older than ones supporting i2cp.leaseSetEncType=6,4 (see #35696), so there should be no compatibility issues here.

    Source: https://i2p.net/en/docs/api/samv3/#sam-virtual-streams-connect

  2. i2p: pass base32 address directly to stream connect abbdec2149
  3. fanquake commented at 3:14 AM on October 19, 2026: member
  4. sedited requested review from jonatack on Nov 6, 2026
  5. DrahtBot commented at 8:36 PM on November 9, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Approach ACK jonatack

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

  6. jonatack commented at 4:46 PM on November 10, 2026: member

    Approach ACK / ACK modulo take a look at adding the unit test suggested by ralph (https://review.fish.foo/traces/36570-abbdec21490ffb355c7918bdb3b9b16aa0e75e4b-882-1.html)

    Router versions supporting direct .b32.i2p in SAM STREAM CONNECT DESTINATION that this pull relies on are:

    • Java I2P 0.9.48: released 30 November 2020
    • i2pd 2.38.0: released 17 May 2021

    so we're ok on that point.

    I don't think the ongoing private broadcast work would be affected by this change, seems orthogonal.

    cc @vasild who implemented I2P support here

  7. jonatack commented at 4:53 PM on November 10, 2026: member

    Here is a sample unit test (LLM generated) to try working with. It compiles and passes on this branch and fails on master.

    <details><summary>diff</summary><p>

    diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp
    index 4f9c89ee1cf..75c370ee469 100644
    --- a/src/test/i2p_tests.cpp
    +++ b/src/test/i2p_tests.cpp
    @@ -172,4 +172,89 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
         }
     }
     
    +BOOST_AUTO_TEST_CASE(connect_direct_b32_and_peer_failure)
    +{
    +    // Valid I2P address; port must be I2P_SAM31_PORT (0).
    +    const std::string b32 = "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p";
    +    CService peer;
    +    BOOST_REQUIRE(peer.SetSpecial(b32));
    +    BOOST_REQUIRE_EQUAL(peer.GetPort(), 0);
    +
    +    // 387-byte private key with cert_len=0 so MyDestination() succeeds.
    +    std::string privkey(387, '\0');
    +    // bytes 385-386 are already 0 (big-endian cert length).
    +    const auto key_file = m_args.GetDataDirNet() / "test_i2p_private_key_direct";
    +    BOOST_REQUIRE(WriteBinaryFile(key_file, privkey));
    +
    +    auto interrupt = std::make_shared<CThreadInterrupt>();
    +    const CService sam_addr{in6_addr(COMPAT_IN6ADDR_LOOPBACK_INIT), 7656};
    +    const Proxy sam_proxy{sam_addr, /*tor_stream_isolation=*/false};
    +
    +    // Factory that returns a DynSock already pre-loaded with the replies
    +    // Connect() will consume. This removes the race between socket creation
    +    // and PushBytes.
    +    auto make_connect_sock = [](const std::string& result_code) {
    +        auto pipes = std::make_shared<DynSock::Pipes>();
    +        const std::string hello = "HELLO REPLY RESULT=OK VERSION=3.1\n";
    +        const std::string status = "STREAM STATUS RESULT=" + result_code + " MESSAGE=\"test\"\n";
    +        pipes->recv.PushBytes(hello.data(), hello.size());
    +        pipes->recv.PushBytes(status.data(), status.size());
    +        return std::make_pair(std::make_unique<DynSock>(pipes), pipes);
    +    };
    +
    +    std::shared_ptr<DynSock::Pipes> last_connect_pipes;
    +    size_t sock_idx{0};
    +
    +    CreateSock = [&](int, int, int) -> std::unique_ptr<Sock> {
    +        ++sock_idx;
    +        if (sock_idx == 1) {
    +            // Session-creation control socket (private key already on disk).
    +            return std::make_unique<StaticContentsSock>(
    +                "HELLO REPLY RESULT=OK VERSION=3.1\n"
    +                "SESSION STATUS RESULT=OK\n"
    +                "a"  // keep socket open
    +            );
    +        }
    +        // Connect-path sockets. The result code is chosen by the caller
    +        // before Connect() is invoked; we stash the pipes for later inspection.
    +        std::string result = (sock_idx == 2) ? "CANT_REACH_PEER" : "TIMEOUT";
    +        auto [sock, pipes] = make_connect_sock(result);
    +        last_connect_pipes = pipes;
    +        return std::move(sock);
    +    };
    +
    +    i2p::sam::Session session(key_file, sam_proxy, interrupt);
    +
    +    auto check = [&](const std::string& expected_result) {
    +        i2p::Connection conn;
    +        bool proxy_error{true};
    +        BOOST_CHECK_MESSAGE(!session.Connect(peer, conn, proxy_error),
    +                            "Connect must fail");
    +        BOOST_CHECK_MESSAGE(!proxy_error,
    +                            expected_result + " must set proxy_error=false");
    +
    +        BOOST_REQUIRE(last_connect_pipes);
    +        std::string sent;
    +        char buf[4096];
    +        ssize_t n;
    +        while ((n = last_connect_pipes->send.GetBytes(buf, sizeof(buf))) > 0) {
    +            sent.append(buf, n);
    +        }
    +
    +        BOOST_CHECK_MESSAGE(sent.find("NAMING LOOKUP") == std::string::npos,
    +                            "NAMING LOOKUP must not appear");
    +        BOOST_CHECK_MESSAGE(sent.find("HELLO VERSION MIN=3.1 MAX=3.1") != std::string::npos,
    +                            "HELLO expected");
    +        BOOST_CHECK_MESSAGE(sent.find("STREAM CONNECT") != std::string::npos,
    +                            "STREAM CONNECT expected");
    +        BOOST_CHECK_MESSAGE(sent.find("DESTINATION=" + b32) != std::string::npos,
    +                            "DESTINATION must be the raw .b32.i2p");
    +        BOOST_CHECK_MESSAGE(sent.find("SILENT=false") != std::string::npos,
    +                            "SILENT=false expected");
    +    };
    +
    +    check("CANT_REACH_PEER");
    +    check("TIMEOUT");
    +}
    

    </p></details>


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-11-14 18:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me