Merge bitcoin/bitcoin#35867: test: classify SOCKS5 peers via getpeerinfo addrbind

4e8c4bc794 test: classify SOCKS5 peers via getpeerinfo addrbind (Henry Romp)

Pull request description:

  p2p_private_broadcast.py classifies each SOCKS5 connection by scanning the node's debug log for `trying v. connection (...) to <addr>:<port>`, then attaches a fake peer for that type. The helper returned the first match in the whole log, so when a feeler selected a clearnet address that private broadcast had used earlier in the run (in the CI failure, `[50::1]:8333`, about 10 seconds apart), the feeler was labelled private-broadcast, was given the `NoRelayP2PInterface`, and disconnected as a feeler rather than with the expected "connected in vain" message.

  Instead of relying on the debug log, identify the connection via the SOCKS5 proxy client socket's source address, which equals the node's `addrbind` for that peer, and read `connection_type` from getpeerinfo. The proxy replies to the SOCKS5 request before invoking `destinations_factory`, so the node has already registered the peer by the time classification runs. This also stops treating debug.log contents as a stable test interface. Dropping the log scrape removes a full re-read of debug.log per SOCKS5 connection; `p2p_private_broadcast.py` goes from ~23s to ~14s locally.

  Fixes #35843

  Tested with:
  `build/test/functional/test_runner.py p2p_private_broadcast.py p2p_private_broadcast_retry_v1.py --timeout-factor=2`, and against the forced-feeler repro from the issue, which no longer mislabels the feeler.

ACKs for top commit:
  jeanpablojp:
    tACK 4e8c4bc794
  andrewtoth:
    ACK 4e8c4bc794
  mzumsande:
    Code Review ACK 4e8c4bc794

Tree-SHA512: ce2db418787d7ecf518bd49b37d7d664748fee5991a2924522dfaf42b27d90ca001caa0611011310636b453d3aada1061d086f20bb866eb66605645935f55c74
This commit is contained in:
merge-script
2026-08-12 17:41:34 +01:00
3 changed files with 27 additions and 26 deletions

View File

@@ -104,6 +104,7 @@ class Socks5Configuration():
# and it decides where the connection is redirected to. It is passed:
# - the address the client requested to connect to
# - the port the client requested to connect to
# - the client's socket address as seen by the proxy, formatted as host:port
# It is supposed to return an object like:
# {
# "actual_to_addr": "127.0.0.1"
@@ -140,8 +141,9 @@ class Socks5Connection():
"""Handle socks5 request according to RFC1928."""
log_exception_prefix = "Socks5Connection.handle(): "
try:
proxy_client = format_sock(self.conn, local=False)
log_exception_prefix = ("Socks5Connection.handle("
f"client={format_sock(self.conn, local=False)}, "
f"client={proxy_client}, "
f"proxy={format_sock(self.conn, local=True)}): ")
# Verify socks version
@@ -193,7 +195,9 @@ class Socks5Connection():
port_hi,port_lo = recvall(self.conn, 2)
port = (port_hi << 8) | port_lo
# Send dummy response
# Reply SUCCESS before calling destinations_factory, so the client can finish
# establishing the connection and register the peer; factories that consult
# getpeerinfo depend on that order.
self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
cmdin = Socks5Command(cmd, atyp, addr, port, username, password)
@@ -205,7 +209,7 @@ class Socks5Connection():
if self.serv.is_running():
if self.serv.conf.destinations_factory is not None:
dest = self.serv.conf.destinations_factory(requested_to_addr, port)
dest = self.serv.conf.destinations_factory(requested_to_addr, port, proxy_client)
if dest is not None:
logger.debug(f"Serving connection to {requested_to}, will redirect it to "
f"{dest['actual_to_addr']}:{dest['actual_to_port']} instead")