Skip to content

IPv6 Support in Tapyrus-Core — Comprehensive Update Plan #434

Description

@Naviabheeman

What is already correct (no changes needed)

CNetAddr/CService/CSubNet primitives store all addresses in 16-byte IPv6 space and implement the full set of RFC classification predicates. SplitHostPort correctly parses [::1]:port bracket notation.
LookupHost/Lookup/LookupIntern use AF_UNSPEC with getaddrinfo, resolving both A and AAAA records.
CreateSocket derives AF_INET6 from GetSockAddr().
BindListenPort creates AF_INET6 sockets and sets IPV6_V6ONLY.
CConnman::InitBinds without explicit -bind creates both IN6ADDR_ANY and INADDR_ANY sockets.
getifaddrs discovery handles both address families.
GetReachabilityFrom scores NET_IPV6 correctly.
The HTTP server default already binds ::1 alongside 127.0.0.1. rpcallowip and ban subnets work via CSubNet which is family-agnostic. The SeedSpec6 format in chainparams is IPv6-capable (seeds are simply absent, which is a deployment decision, not a code gap).


Goal 1 — P2P layer: listen, connect, address relay, banning

Status: Socket creation, binding, and accept paths are correct. Two edge cases need fixing.

Tasks:

  1. scopeId serialization (src/netaddress.h:94): The scopeId field for link-local addresses (fe80::/10) is not serialized. It is lost when an address is written to peers.dat or sent in a P2P addr message. Either serialize it alongside the 16-byte ip[] array, or document explicitly that link-local addresses are unsupported and add an IsLocal() guard before storing them in addrman.
  2. Verify -bind [::]:port dual-listen: InitBinds creates separate IN6ADDR_ANY (with IPV6_V6ONLY) and INADDR_ANY sockets. Trace and confirm there is no early-exit that silently skips the IPv4 bind after IPv6 succeeds (BF_REPORT_ERROR logic at net.cpp:2269).
  3. Verify -externalip [2001:db8::1]: The address goes through Lookup() → CService → AddLocal(). Confirm the resulting local address is advertised correctly in VERSION and addr self-announce messages.
  4. Verify subnet ban end-to-end: setban "2001:db8::/48" → LookupSubNet → CSubNet → CBanDB → reload. Confirm a peer from 2001:db8::1 is rejected at AcceptConnection.
  5. Check addrman group assignment for IPv6 (src/netaddress.cpp:GetGroup): IPv6 uses a /32 group key. Verify there is no off-by-one in GetNewBucket that could cause IPv6 entries to collide with IPv4 entries or with each other.

Goal 2 — Persistence: peers.dat and banlist.dat

Status: 16-byte serialization correctly covers IPv6. scopeId is the one known gap (handled in Goal 1). Addrman bucket arithmetic needs a targeted audit.

Tasks:

  1. Audit CAddrMan::Serialize/Unserialize: Confirm no version gate silently downgrades or drops IPv6 CNetAddr entries when loading an older-format peers.dat.
  2. Audit GetNewBucket for IPv6: The bucket calculation uses GetGroup() which returns the /32 prefix for native IPv6. Verify this does not produce a collision with the IPv4-mapped range (::FFFF:x.x.x.x/32), causing IPv4 and IPv6 entries to share a bucket unintentionally.
  3. Ban persistence for IPv6 subnets: CSubNet with a 128-bit netmask serializes all 16 bytes of both network.ip and the mask array. Confirm a /48 ban survives a shutdown/restart cycle by tracing the read path in CBanDB::Read.

Goal 3 — Config and startup

Status: Parsing is correct. Help text and one UPnP case need attention.

Tasks:

  1. Help text: The -addnode, -connect, -whitelist, and -externalip help strings do not show IPv6 bracket syntax. Add [host]:port examples to each.
  2. RPC help text: -rpcbind, -rpcallowip, and -rpcconnect similarly. Add [::1]:8332 and 2001:db8::/32 examples.
  3. UPnP and IPv6 external IP: UPnP IGD does not provide IPv6 external address discovery. Add an explicit early-return with a log message when UPnP runs and the node is IPv6-only (i.e., NET_IPV4 is unreachable), so the user gets a clear diagnostic instead of silent failure.

Goal 4 — ZMQ

Status: Works if the user provides correct tcp://[::1]:port syntax, but IPv6 is entirely undocumented and the ZMQ_IPV6 socket option is never set, which causes silent bind failure on some libzmq builds.

Tasks:

  1. Set ZMQ_IPV6 socket option (src/zmq/zmqpublishnotifier.cpp:81): Call zmq_setsockopt(psocket, ZMQ_IPV6, 1, sizeof(int)) immediately before zmq_bind. Without this, libzmq may reject tcp://[::1]:port or fall back to IPv4-only even when an IPv6 address is specified.
  2. Document endpoint syntax: Add tcp://[::1]:28332 as an example in the -zmqpub* help strings in src/init.cpp.

Goal 5 — HTTP/RPC server

Status: Default binds already include ::1. The libevent layer handles evhttp_bind_socket_with_handle("::1", port) correctly in libevent ≥ 2.1.x. One asymmetry and one platform limitation exist.

Tasks:

  1. Verify evhttp IPv6 with the minimum supported libevent version: evhttp_bind_socket_with_handle with a bare "::1" string (not bracketed) relies on libevent calling getaddrinfo internally. Confirm this works with the actual minimum libevent version in depends/packages/libevent.mk. If the minimum is below 2.1.x, replace the call with manual socket creation + evhttp_accept_socket.
  2. Loopback allow-list asymmetry (src/httpserver.cpp:173): IPv4 loopback allows the entire 127.0.0.0/8 range; IPv6 loopback allows only ::1/128. This is more restrictive but asymmetric. Document the behaviour in a comment; adjust if needed.
  3. all_interfaces() in test framework (test/functional/test_framework/netutil.py:89): Uses SIOCGIFCONF which returns IPv4 interface addresses only. IPv6 non-loopback interface discovery requires getifaddrs or parsing /proc/net/if_inet6 on Linux. Fix for non-loopback IPv6 RPC bind tests in Goal 7.

Goal 6 — Tor

Status: Two blocking bugs and one deprecation to fix.

Tasks:

  1. ADD_ONION hardcoded IPv4 target (src/torcontrol.cpp:553): The hidden service target is hardcoded as 127.0.0.1:. If the node only listens on IPv6 loopback (::1), Tor cannot forward inbound hidden-service traffic. Change to detect the actual bound address (or use [::1]: when the node is IPv6-only) and format accordingly.
  2. Tor v2 → v3 migration (src/torcontrol.cpp:549): NEW:RSA1024 requests a v2 onion service. Tor removed v2 support in 0.4.6 (2021). Change to NEW:ED25519-V3 and update the address encoding and parsing to handle 56-character v3 .onion addresses instead of 16-character v2. This is not strictly IPv6 but blocks Tor from working at all on modern Tor daemons.
  3. Tor proxy on IPv6 loopback: -proxy [::1]:9050 (Tor SOCKS5 on IPv6 loopback) passes through SplitHostPort and CreateSocket correctly. Verify auth_cb in torcontrol.cpp also uses the configured proxy address rather than hardcoding 127.0.0.1:9050 for the Tor control connection.

Goal 7 — Functional and regression tests (IPv4 + IPv6 combination testing)

Tasks:

  1. P2P IPv6 connectivity test (new): Start two nodes with -bind [::1]: and connect them. Confirm handshake completes, blocks propagate, and getpeerinfo shows an IPv6 peer address. Guard with test_ipv6_local().
  2. Dual-stack P2P test (new): Start a node bound to both 127.0.0.1:<p> and [::1]:<p>. Connect one peer via each. Confirm both appear in getpeerinfo with correct address families simultaneously.
  3. IPv6 addr relay test (test/functional/p2p_addr_relay.py): The test only sends 123.123.x.x IPv4 addresses. Add a parallel case that sends 2001:db8::x IPv6 addresses via addr message and verifies they are stored in addrman and relayed to a third peer.
  4. IPv6 ban test (new or extend rpc_ban.py): Call setban "::1/128", confirm a connection from [::1] is refused. Call setban "2001:db8::/32", confirm the subnet is persisted in banlist.dat and reloaded after restart.
  5. ZMQ IPv6 endpoint test (new): Bind a ZMQ publisher to tcp://[::1]:, subscribe from the same address, mine a block, and confirm the hashblock notification arrives. Guard with test_ipv6_local().
  6. HTTP/RPC IPv6 test (extend rpc_bind.py): Add a case that binds only to [::1], confirms RPC calls succeed via [::1], and confirms 127.0.0.1 is refused. Fix all_interfaces() to use /proc/net/if_inet6 or getifaddrs so non-loopback IPv6 interfaces are discoverable on Linux.
  7. addrman IPv6 round-trip test (new, src/test/addrman_tests.cpp): Insert an IPv6 CAddress (e.g. 2001:db8::1) as both address and source, serialize addrman to a stream, deserialize it, and confirm the entry is present with the correct address. This closes the only gap in the addrman unit tests.
  8. peers.dat round-trip with IPv6 (new, src/test/addrman_tests.cpp or separate): Write addrman containing IPv6 entries to a temporary file via CAddrDB::Write, read it back via CAddrDB::Read, and confirm all entries are intact.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions