Repository navigation
net: give SOCKS dials a 30 s connect bound - #1104
Conversation
88ae133 to
41930da
Compare
There was a problem hiding this comment.
Review
The three production callers of connect_timeout_for now go through Dialer::connect_timeout, and the free function is gone. Proxied clearnet (proxy: Some) and onion addresses use base.max(SOCKS_CONNECT_TIMEOUT). I2P stays base.max(90s). Direct IP and CJDNS stay on base. Duration::max does not shrink a larger base. An onion-only dialer still connects clearnet directly, and that address stays on base. I2P is matched before the SOCKS arms, so a proxied dialer still gives I2P 90s.
| Severity | Location | Finding |
|---|---|---|
| Medium | crates/rbitcoin-net/src/socks.rs:89 |
The 30s constant is cfg(not(test)). Tests compile 3s. overlay_and_socks_connects_outlast_a_direct_one asserts a SOCKS result > 1s, so a production floor of 8s or 300s still passes. The test never calls dial_batch, and it does not cover CJDNS, a proxied I2P address, or a base already above the floor. |
| Medium | crates/rbitcoin-net/src/ibd/dial.rs:306 |
IBD wraps spawn_peer (SOCKS connect, then connect_and_handshake_timed with HANDSHAKE_TIMEOUT of 60s) in this single timer. Handshake time comes out of the same 30s. prepare_outbound_session applies the helper to the connect future only and then allows another 60s of handshake. Tip-follow uses the helper as the outer bound around follow_from_net_as (run.rs), so a connect that uses most of the 30s leaves the handshake inside that same window. |
| Low | crates/rbitcoin-net/src/ibd/mod.rs:361 |
The initial dial awaits the whole batch. Default max_outbound is 16, so the wave is 32 tasks, and slots are admitted after every dial finishes. One unanswered SOCKS peer holds the wave for the full floor and holds one fd per dial. These are tokio tasks. Later redials are already off the IBD loop. |
Call sites on the parent:
| Parent | Head |
|---|---|
dial_batch |
dialer.connect_timeout(addr, connect_timeout) |
prepare_outbound_session |
dialer.connect_timeout(peer_net, OUTBOUND_CONNECT_TIMEOUT) |
follow_connect_timeout |
dialer.connect_timeout(peer, 8s) |
fuzz uses TcpStream::connect_timeout, which is a different function. The 3s test constant is what lets redial_skips_a_connect_target_still_dialling finish: its deadline is OUTBOUND_CONNECT_TIMEOUT * 3 (6s under cfg(test)), and a proxied dial now waits max(2s, 3s).
Pin the production floor with an explicit 30s expectation outside cfg(test) (or a const the test can see), and decide whether the IBD and tip-follow timers should cover connect only, with the handshake on its own 60s budget the way prepare_outbound_session already does.
|
The |
|
Thanks. Addressed in
The |
Every isolated SOCKS dial builds its own Tor circuit. A 32-dial IBD wave queues inside Tor for a median of 7 s on a healthy client, so the 8 s connect + handshake bound timed out whole waves. Onion dials went through the same 8 s. Dialer::connect_timeout replaces connect_timeout_for at every caller (IBD dial_batch, prepare_outbound_session, tip-follow). Proxied clearnet and onion dials get at least the Dialer::Socks connect_floor, 30 s from with_proxies. Direct and CJDNS dials keep the base, and I2P keeps 90 s. IBD and tip-follow bound connect and handshake together, as before; prepare_outbound_session bounds only the connect and keeps HANDSHAKE_TIMEOUT for the handshake. Tests that wait out a stalled proxy lower the floor with with_connect_floor. Rebased onto the crate-layout move as one commit: the earlier cfg(test) floor cannot reach the tests now under tests/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61fbe47 to
1094e98
Compare
|
Rebased onto |
There was a problem hiding this comment.
Review
1094e987 replaces the cfg(test) 3s constant. connect_floor is a Dialer::Socks field with no default, set to 30s in with_proxies, which is what production dialers use. A Socks { ... } literal that omits it does not compile. with_connect_floor is doc(hidden) and only the stalled-proxy redial test and the floor unit check call it.
overlay_and_socks_connects_outlast_a_direct_one asserts equality with 30s for proxied clearnet and onion, 90s for I2P on a direct or proxied dialer, base for direct, CJDNS, and onion-only clearnet, and keeps a 120s base. The three former connect_timeout_for callers go through Dialer::connect_timeout. IBD dial_batch and tip-follow still wrap connect and handshake in that one bound; the rustdoc says so, and prepare_outbound_session still bounds only the connect before the separate 60s handshake.
Problem
Each isolated SOCKS dial (
--proxy-randomize, the default) builds its own Tor circuit. On a healthy Tor client:An IBD wave is 32 dials, and its bound covers connect plus handshake, so 8 s timed out whole waves. Onion dials used the same 8 s.
Fix
Dialer::connect_timeout(addr, base)replaces the freeconnect_timeout_forat every caller (IBDdial_batch,prepare_outbound_session, tip-follow):--proxy, and onion:base.max(connect_floor).connect_flooris aDialer::Socksfield, 30 s fromwith_proxies.base.max(90 s)(unchanged)base(unchanged)Scope, now in the rustdoc: IBD
dial_batchand tip-follow wrap connect and handshake in this one bound, as they did with 8 s, so a slow peer is skipped rather than held through a wave.prepare_outbound_sessionbounds only the connect with it and keeps the 60 sHANDSHAKE_TIMEOUTfor the handshake.Tests
overlay_and_socks_connects_outlast_a_direct_one(crates/rbitcoin-ibd/tests/ibd/dial.rs) pins the production values: 30 s for proxied clearnet and onion; 90 s for I2P, direct or proxied;basefor direct, CJDNS and an onion-only dialer with a clearnet address; a base above a floor (120 s) kept; andwith_connect_floorlowering it.redial_skips_a_connect_target_still_dialling(crates/rbitcoin-net/tests/service.rs) waits out a stalled proxy with a lowered floor (#[doc(hidden)] with_connect_floor), not acfg(test)constant.Rebased onto the crate-layout move (#1097) as one commit: the earlier two-commit history used a
cfg(test)floor that can't reach the tests now undertests/.🤖 Generated with Claude Code