zebra_network/peer_set/initialize.rs
1//! A peer set whose size is dynamically determined by resource constraints.
2//!
3//! The [`PeerSet`] implementation is adapted from the one in [tower::Balance][tower-balance].
4//!
5//! [tower-balance]: https://github.com/tower-rs/tower/tree/master/tower/src/balance
6
7use std::{
8 collections::{BTreeMap, HashMap, HashSet},
9 convert::Infallible,
10 net::{IpAddr, SocketAddr},
11 sync::Arc,
12 time::Duration,
13};
14
15use futures::{future::FutureExt, sink::SinkExt, stream::StreamExt, TryFutureExt};
16use rand::seq::SliceRandom;
17use tokio::{
18 net::{TcpListener, TcpStream},
19 sync::{broadcast, mpsc, watch},
20 task::JoinSet,
21 time::{sleep, Instant},
22};
23use tokio_stream::wrappers::IntervalStream;
24use tower::{discover::Change, layer::Layer, Service, ServiceExt};
25use tracing_futures::Instrument;
26
27use zebra_chain::{
28 chain_tip::ChainTip,
29 diagnostic::task::{CheckForPanics, WaitForPanics},
30 parameters::Network,
31};
32
33use crate::{
34 address_book_updater::{
35 AddressBookChangeSender, AddressBookService, AddressBookUpdater, MIN_CHANNEL_SIZE,
36 },
37 connection_metrics::{
38 network_kind_label, record_connection_attempt_finished, record_connection_attempt_started,
39 record_inbound_connection_rejected, ConnectionDirection,
40 },
41 constants,
42 meta_addr::MetaAddr,
43 peer::{
44 self, address_is_valid_for_inbound_listeners, HandshakeRequest, MinimumPeerVersion,
45 OutboundConnectorRequest, PeerPreference,
46 },
47 peer_cache_updater::peer_cache_updater,
48 peer_set::{
49 crawl_once, crawler_services, next_reconnect_peer, ready_peer_count, set::MorePeers,
50 ActiveConnectionCounter, ConnectionTracker, CrawlService, NextPeerService, PeerSet,
51 },
52 protocol::external::{canonical_peer_addr, canonical_socket_addr},
53 AddressBook, BanList, BoxError, Config, MisbehaviorReport, NetworkService, PeerSocketAddr,
54 Request, Response,
55};
56
57#[cfg(test)]
58mod tests;
59
60mod recent_by_ip;
61
62/// A successful outbound peer connection attempt or inbound connection handshake.
63///
64/// The [`Handshake`](peer::Handshake) service returns a [`Result`]. Only successful connections
65/// should be sent on the channel. Errors should be logged or ignored.
66///
67/// We don't allow any errors in this type, because:
68/// - The connection limits don't include failed connections
69/// - tower::Discover interprets an error as stream termination
70type DiscoveredPeer = (PeerSocketAddr, peer::Client);
71
72/// Initialize a peer set, using a network `config`, `inbound_service`,
73/// and `latest_chain_tip`.
74///
75/// The peer set abstracts away peer management to provide a
76/// [`tower::Service`] representing "the network" that load-balances requests
77/// over available peers. The peer set automatically crawls the network to
78/// find more peer addresses and opportunistically connects to new peers.
79///
80/// Each peer connection's message handling is isolated from other
81/// connections, unlike in `zcashd`. The peer connection first attempts to
82/// interpret inbound messages as part of a response to a previously-issued
83/// request. Otherwise, inbound messages are interpreted as requests and sent
84/// to the supplied `inbound_service`.
85///
86/// Wrapping the `inbound_service` in [`tower::load_shed`] middleware will
87/// cause the peer set to shrink when the inbound service is unable to keep up
88/// with the volume of inbound requests.
89///
90/// Releasing the last [`NetworkService`] handle and unfinished response stops the network's
91/// background tasks and releases its listener and peer connections. Internal polling and
92/// crawling do not keep the network alive.
93/// Services retained by `inbound_service` must use [`NetworkService::background_service`]
94/// rather than retain an owning network handle.
95///
96/// Use [`NoChainTip`][1] to explicitly provide no chain tip receiver.
97///
98/// In addition to returning a service for outbound requests, this method
99/// returns a shared [`AddressBook`] updated with last-seen timestamps for
100/// connected peers. The shared address book should be accessed using a
101/// [blocking thread](https://docs.rs/tokio/1.15.0/tokio/task/index.html#blocking-and-yielding),
102/// to avoid async task deadlocks.
103///
104/// # Panics
105///
106/// If `config.config.peerset_initial_target_size` is zero.
107/// (zebra-network expects to be able to connect to at least one peer.)
108///
109/// [1]: zebra_chain::chain_tip::NoChainTip
110pub async fn init<S, C>(
111 config: Config,
112 inbound_service: S,
113 latest_chain_tip: C,
114 user_agent: String,
115) -> (
116 NetworkService,
117 Arc<std::sync::Mutex<AddressBook>>,
118 mpsc::Sender<MisbehaviorReport>,
119)
120where
121 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
122 S::Future: Send + 'static,
123 C: ChainTip + Clone + Send + Sync + 'static,
124{
125 init_with_block_gossip_peer_ips(
126 config,
127 inbound_service,
128 latest_chain_tip,
129 user_agent,
130 Vec::new(),
131 )
132 .await
133}
134
135/// Initialize a peer set like [`init`], additionally pinning block inventory
136/// broadcasts to the inbound peers at `block_gossip_peer_ips`.
137///
138/// Inbound peers connecting from these IP addresses always receive block
139/// inventory broadcasts, and one inbound connection slot is reserved for them.
140/// This is used by zcashd-compat mode, where a zcashd wallet sidecar makes a
141/// single P2P connection to this node and must reliably learn about new blocks.
142pub async fn init_with_block_gossip_peer_ips<S, C>(
143 config: Config,
144 inbound_service: S,
145 latest_chain_tip: C,
146 user_agent: String,
147 block_gossip_peer_ips: Vec<IpAddr>,
148) -> (
149 NetworkService,
150 Arc<std::sync::Mutex<AddressBook>>,
151 mpsc::Sender<MisbehaviorReport>,
152)
153where
154 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
155 S::Future: Send + 'static,
156 C: ChainTip + Clone + Send + Sync + 'static,
157{
158 let (tcp_listener, listen_addr) = open_listener(&config.clone()).await;
159
160 let (
161 address_book,
162 bans_receiver,
163 address_book_updater,
164 address_book_service,
165 address_metrics,
166 address_book_updater_guard,
167 ) = AddressBookUpdater::spawn(&config, listen_addr);
168
169 let (misbehavior_tx, mut misbehavior_rx) = mpsc::channel::<MisbehaviorReport>(
170 // Leave enough room for a misbehaviour update on every peer connection
171 // before the channel is drained.
172 config
173 .peerset_total_connection_limit()
174 .max(MIN_CHANNEL_SIZE),
175 );
176
177 let misbehaviour_updater = address_book_updater.clone();
178 let misbehavior_guard = tokio::spawn(
179 async move {
180 let mut misbehaviors: HashMap<PeerSocketAddr, MisbehaviorReport> = HashMap::new();
181 // Batch misbehaviour updates so peers can't keep the address book mutex locked
182 // by repeatedly sending invalid blocks or transactions.
183 let mut flush_timer =
184 IntervalStream::new(tokio::time::interval(constants::MISBEHAVIOR_FLUSH_INTERVAL));
185
186 loop {
187 tokio::select! {
188 msg = misbehavior_rx.recv() => match msg {
189 Some(report) => {
190 // Keep the first reason: any report can cross the ban threshold.
191 let score = report.score;
192 misbehaviors
193 .entry(report.addr)
194 .and_modify(|batched| {
195 batched.score = batched.score.saturating_add(score)
196 })
197 .or_insert(report);
198 }
199 None => break,
200 },
201
202 _ = flush_timer.next() => {
203 for (_, report) in misbehaviors.drain() {
204 let _ = misbehaviour_updater
205 .send(MetaAddr::new_misbehavior(
206 report.addr,
207 report.score,
208 report.reason,
209 ))
210 .await;
211 }
212 },
213 };
214 }
215
216 tracing::warn!("exiting misbehavior update batch task");
217 }
218 .in_current_span(),
219 );
220
221 // Create a broadcast channel for peer inventory advertisements.
222 // If it reaches capacity, this channel drops older inventory advertisements.
223 //
224 // When Zebra is at the chain tip with an up-to-date mempool,
225 // we expect to have at most 1 new transaction per connected peer,
226 // and 1-2 new blocks across the entire network.
227 // (The block syncer and mempool crawler handle bulk fetches of blocks and transactions.)
228 let (inv_sender, inv_receiver) = broadcast::channel(config.peerset_total_connection_limit());
229
230 // Construct services that handle inbound handshakes and perform outbound
231 // handshakes. These use the same handshake service internally to detect
232 // self-connection attempts. Both are decorated with a tower TimeoutLayer to
233 // enforce timeouts as specified in the Config.
234 let (listen_handshaker, outbound_connector) = {
235 use tower::timeout::TimeoutLayer;
236 let hs_timeout = TimeoutLayer::new(constants::HANDSHAKE_TIMEOUT);
237 use crate::protocol::external::types::PeerServices;
238 let hs = peer::Handshake::builder()
239 .with_config(config.clone())
240 .with_inbound_service(inbound_service)
241 .with_inventory_collector(inv_sender)
242 .with_address_book_updater(address_book_updater.clone())
243 .with_advertised_services(PeerServices::NODE_NETWORK)
244 .with_user_agent(user_agent)
245 .with_latest_chain_tip(latest_chain_tip.clone())
246 .want_transactions(true)
247 .finish()
248 .expect("configured all required parameters");
249 (
250 hs_timeout.layer(hs.clone()),
251 hs_timeout.layer(peer::Connector::new(hs)),
252 )
253 };
254
255 // Create an mpsc channel for peer changes,
256 // based on the maximum number of inbound and outbound peers.
257 //
258 // The connection limit does not apply to errors,
259 // so they need to be handled before sending to this channel.
260 let (peerset_tx, peerset_rx) =
261 futures::channel::mpsc::channel::<DiscoveredPeer>(config.peerset_total_connection_limit());
262
263 let discovered_peers = peerset_rx.map(|(address, client)| {
264 Result::<_, Infallible>::Ok(Change::Insert(address, client.into()))
265 });
266
267 // Create an mpsc channel for peerset demand signaling,
268 // based on the maximum number of outbound peers.
269 let (mut demand_tx, demand_rx) =
270 futures::channel::mpsc::channel::<MorePeers>(config.peerset_outbound_connection_limit());
271
272 // Create a oneshot to send background task JoinHandles to the peer set
273 let (handle_tx, handle_rx) = tokio::sync::oneshot::channel();
274
275 // Connect the rx end to a PeerSet, wrapping new peers in load instruments.
276 let peer_set = PeerSet::new(
277 &config,
278 block_gossip_peer_ips.clone(),
279 discovered_peers,
280 demand_tx.clone(),
281 handle_rx,
282 inv_receiver,
283 bans_receiver.clone(),
284 address_metrics,
285 MinimumPeerVersion::new(latest_chain_tip, &config.network),
286 None,
287 );
288 // Also route queued peer set requests as soon as a peer that might serve them is ready.
289 let (mut peer_set, poll_peer_set_guard) = peer_set.into_buffer(constants::PEERSET_BUFFER_SIZE);
290 peer_set.extend_tasks([
291 address_book_updater_guard.abort_handle(),
292 misbehavior_guard.abort_handle(),
293 ]);
294
295 // Connect peerset_tx to the 3 peer sources:
296 //
297 // 1. Incoming peer connections, via a listener.
298 let listen_fut = accept_inbound_connections(
299 config.clone(),
300 tcp_listener,
301 constants::MIN_INBOUND_PEER_CONNECTION_INTERVAL,
302 listen_handshaker,
303 peerset_tx.clone(),
304 bans_receiver,
305 block_gossip_peer_ips,
306 );
307 let listen_guard = tokio::spawn(listen_fut.in_current_span());
308 peer_set.extend_tasks([listen_guard.abort_handle()]);
309
310 // 2. Initial peers, specified in the config and cached on disk.
311 let initial_peers_fut = add_initial_peers(
312 config.clone(),
313 outbound_connector.clone(),
314 peerset_tx.clone(),
315 address_book_updater.clone(),
316 );
317 let initial_peers_join = tokio::spawn(initial_peers_fut.in_current_span());
318 peer_set.extend_tasks([initial_peers_join.abort_handle()]);
319
320 // 3. Outgoing peers we connect to in response to load.
321 let (next_peer_service, mut crawl_service) =
322 crawler_services(address_book_service.clone(), peer_set.background_service());
323
324 // Wait for the initial seed peer count
325 let mut active_outbound_connections = initial_peers_join
326 .wait_for_panics()
327 .await
328 .expect("unexpected error connecting to initial peers");
329 let active_initial_peer_count = active_outbound_connections.update_count();
330
331 // We need to await the initial crawl here,
332 // because zcashd rate-limits `addr`/`addrv2` messages per connection,
333 // and if we only have one initial peer,
334 // we need to ensure that its `Response::Addr` is used by the crawler.
335 //
336 // TODO: this might not be needed after we added the Connection peer address cache,
337 // try removing it in a future release?
338 info!(
339 ?active_initial_peer_count,
340 "sending initial request for peers"
341 );
342 let _ = crawl_once(&mut crawl_service, Some(active_initial_peer_count)).await;
343
344 // Compute remaining connections to open.
345 let demand_count = config
346 .peerset_initial_target_size
347 .saturating_sub(active_outbound_connections.update_count());
348
349 for _ in 0..demand_count {
350 let _ = demand_tx.try_send(MorePeers);
351 }
352
353 // Start the peer crawler
354 let crawl_fut = crawl_and_dial(
355 config.clone(),
356 demand_tx,
357 demand_rx,
358 next_peer_service,
359 crawl_service,
360 address_book_service.clone(),
361 outbound_connector,
362 peerset_tx,
363 active_outbound_connections,
364 address_book_updater,
365 );
366 let crawl_guard = tokio::spawn(crawl_fut.in_current_span());
367 peer_set.extend_tasks([crawl_guard.abort_handle()]);
368
369 // Start the peer disk cache updater
370 let peer_cache_updater_fut = peer_cache_updater(config, address_book_service);
371 let peer_cache_updater_guard = tokio::spawn(peer_cache_updater_fut.in_current_span());
372 peer_set.extend_tasks([peer_cache_updater_guard.abort_handle()]);
373
374 handle_tx
375 .send(vec![
376 listen_guard,
377 crawl_guard,
378 address_book_updater_guard,
379 peer_cache_updater_guard,
380 poll_peer_set_guard,
381 ])
382 .unwrap();
383
384 (peer_set, address_book, misbehavior_tx)
385}
386
387/// Use the provided `outbound_connector` to connect to the configured DNS seeder and
388/// disk cache initial peers, then send the resulting peer connections over `peerset_tx`.
389///
390/// Also sends every initial peer address to the `address_book_updater`.
391#[instrument(skip(config, outbound_connector, peerset_tx, address_book_updater))]
392async fn add_initial_peers<S>(
393 config: Config,
394 outbound_connector: S,
395 mut peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
396 address_book_updater: AddressBookChangeSender,
397) -> Result<ActiveConnectionCounter, BoxError>
398where
399 S: Service<
400 OutboundConnectorRequest,
401 Response = (PeerSocketAddr, peer::Client),
402 Error = BoxError,
403 > + Clone
404 + Send
405 + 'static,
406 S::Future: Send + 'static,
407{
408 let initial_peers = limit_initial_peers(&config, address_book_updater).await;
409
410 let mut handshake_success_total: usize = 0;
411 let mut handshake_error_total: usize = 0;
412
413 let mut active_outbound_connections = ActiveConnectionCounter::new_counter_with(
414 config.peerset_outbound_connection_limit(),
415 "Outbound Connections",
416 );
417
418 // TODO: update when we add Tor peers or other kinds of addresses.
419 let ipv4_peer_count = initial_peers.iter().filter(|ip| ip.is_ipv4()).count();
420 let ipv6_peer_count = initial_peers.iter().filter(|ip| ip.is_ipv6()).count();
421 info!(
422 ?ipv4_peer_count,
423 ?ipv6_peer_count,
424 "connecting to initial peer set"
425 );
426
427 // # Security
428 //
429 // Resists distributed denial of service attacks by making sure that
430 // new peer connections are initiated at least `MIN_OUTBOUND_PEER_CONNECTION_INTERVAL` apart.
431 //
432 // Seed attempts belong to this task, including when initialization is cancelled.
433 let network = config.network.clone();
434 let mut handshakes = JoinSet::new();
435 for (i, addr) in initial_peers.into_iter().enumerate() {
436 let connection_tracker = active_outbound_connections.track_connection();
437 let req = OutboundConnectorRequest {
438 addr,
439 connection_tracker,
440 };
441 let outbound_connector = outbound_connector.clone();
442 let network = network.clone();
443
444 // Spawn a new task to make the outbound connection.
445 handshakes.spawn(
446 async move {
447 // Only spawn one outbound connector per
448 // `MIN_OUTBOUND_PEER_CONNECTION_INTERVAL`,
449 // by sleeping for the interval multiplied by the peer's index in the list.
450 sleep(constants::MIN_OUTBOUND_PEER_CONNECTION_INTERVAL.saturating_mul(i as u32))
451 .await;
452
453 // As soon as we create the connector future,
454 // the handshake starts running as a spawned task.
455 record_connection_attempt_started(&network, ConnectionDirection::Outbound, addr);
456 let result = outbound_connector
457 .oneshot(req)
458 .map_err(move |e| (addr, e))
459 .await;
460 record_connection_attempt_finished(
461 &network,
462 ConnectionDirection::Outbound,
463 addr,
464 result.as_ref().err().map(|(_, error)| error),
465 );
466 result
467 }
468 .in_current_span(),
469 );
470 }
471
472 while let Some(handshake_result) = handshakes.join_next().await {
473 let Ok(handshake_result) = handshake_result.panic_if_task_has_panicked() else {
474 continue;
475 };
476 match handshake_result {
477 Ok(change) => {
478 handshake_success_total += 1;
479 debug!(
480 ?handshake_success_total,
481 ?handshake_error_total,
482 ?change,
483 "an initial peer handshake succeeded"
484 );
485
486 // The connection limit makes sure this send doesn't block
487 peerset_tx.send(change).await?;
488 }
489 Err((addr, ref e)) => {
490 handshake_error_total += 1;
491
492 // this is verbose, but it's better than just hanging with no output when there are errors
493 let mut expected_error = false;
494 if let Some(io_error) = e.downcast_ref::<tokio::io::Error>() {
495 // Some systems only have IPv4, or only have IPv6,
496 // so these errors are not particularly interesting.
497 if io_error.kind() == tokio::io::ErrorKind::AddrNotAvailable {
498 expected_error = true;
499 }
500 }
501
502 if expected_error {
503 debug!(
504 successes = ?handshake_success_total,
505 errors = ?handshake_error_total,
506 ?addr,
507 ?e,
508 "an initial peer connection failed"
509 );
510 } else {
511 info!(
512 successes = ?handshake_success_total,
513 errors = ?handshake_error_total,
514 ?addr,
515 %e,
516 "an initial peer connection failed"
517 );
518 }
519 }
520 }
521
522 // Security: Let other tasks run after each connection is processed.
523 //
524 // Avoids remote peers starving other Zebra tasks using initial connection successes or errors.
525 tokio::task::yield_now().await;
526 }
527
528 let outbound_connections = active_outbound_connections.update_count();
529 info!(
530 ?handshake_success_total,
531 ?handshake_error_total,
532 ?outbound_connections,
533 "finished connecting to initial seed and disk cache peers"
534 );
535
536 Ok(active_outbound_connections)
537}
538
539/// Limit the number of `initial_peers` addresses entries to the configured
540/// `peerset_initial_target_size`.
541///
542/// Returns randomly chosen entries from the provided set of addresses,
543/// in a random order.
544///
545/// Also sends every initial peer to the `address_book_updater`.
546async fn limit_initial_peers(
547 config: &Config,
548 address_book_updater: AddressBookChangeSender,
549) -> HashSet<PeerSocketAddr> {
550 let all_peers: HashSet<PeerSocketAddr> = config.initial_peers().await;
551 let mut preferred_peers: BTreeMap<PeerPreference, Vec<PeerSocketAddr>> = BTreeMap::new();
552
553 let all_peers_count = all_peers.len();
554 if all_peers_count > config.peerset_initial_target_size {
555 info!(
556 "limiting the initial peers list from {} to {}",
557 all_peers_count, config.peerset_initial_target_size,
558 );
559 }
560
561 // Filter out invalid initial peers, and prioritise valid peers for initial connections.
562 // (This treats initial peers the same way we treat gossiped peers.)
563 for peer_addr in all_peers {
564 let preference = PeerPreference::new(peer_addr, config.network.clone());
565
566 match preference {
567 Ok(preference) => preferred_peers
568 .entry(preference)
569 .or_default()
570 .push(peer_addr),
571 Err(error) => info!(
572 ?peer_addr,
573 ?error,
574 "invalid initial peer from DNS seeder, configured IP address, or disk cache",
575 ),
576 }
577 }
578
579 // Send every initial peer to the address book, in preferred order.
580 // (This treats initial peers the same way we treat gossiped peers.)
581 //
582 // # Security
583 //
584 // Initial peers are limited because:
585 // - the number of initial peers is limited
586 // - this code only runs once at startup
587 for peer in preferred_peers.values().flatten() {
588 let peer_addr = MetaAddr::new_initial_peer(*peer);
589 // `send` only waits when the channel is full.
590 // The address book updater runs in its own thread, so we will only wait for a short time.
591 let _ = address_book_updater.send(peer_addr).await;
592 }
593
594 // Split out the `initial_peers` that will be shuffled and returned,
595 // choosing preferred peers first.
596 let mut initial_peers: HashSet<PeerSocketAddr> = HashSet::new();
597 for better_peers in preferred_peers.values() {
598 let mut better_peers = better_peers.clone();
599 let (chosen_peers, _unused_peers) = better_peers.partial_shuffle(
600 &mut rand::rng(),
601 config.peerset_initial_target_size - initial_peers.len(),
602 );
603
604 initial_peers.extend(chosen_peers.iter());
605
606 if initial_peers.len() >= config.peerset_initial_target_size {
607 break;
608 }
609 }
610
611 initial_peers
612}
613
614/// Open a peer connection listener on `config.listen_addr`,
615/// returning the opened [`TcpListener`], and the address it is bound to.
616///
617/// If the listener is configured to use an automatically chosen port (port `0`),
618/// then the returned address will contain the actual port.
619///
620/// # Panics
621///
622/// If opening the listener fails.
623#[instrument(skip(config), fields(addr = ?config.listen_addr))]
624pub(crate) async fn open_listener(config: &Config) -> (TcpListener, SocketAddr) {
625 // Warn if we're configured using the wrong network port.
626 if let Err(wrong_addr) =
627 address_is_valid_for_inbound_listeners(config.listen_addr, config.network.clone())
628 {
629 warn!(
630 "We are configured with address {} on {:?}, but it could cause network issues. \
631 The default port for {:?} is {}. Error: {wrong_addr:?}",
632 config.listen_addr,
633 config.network,
634 config.network,
635 config.network.default_port(),
636 );
637 }
638
639 info!(
640 "Trying to open Zcash protocol endpoint at {}...",
641 config.listen_addr
642 );
643 let listener_result = TcpListener::bind(config.listen_addr).await;
644
645 let listener = match listener_result {
646 Ok(l) => l,
647 Err(e) => panic!(
648 "Opening Zcash network protocol listener {:?} failed: {e:?}. \
649 Hint: Check if another zebrad or zcashd process is running. \
650 Try changing the network listen_addr in the Zebra config.",
651 config.listen_addr,
652 ),
653 };
654
655 let local_addr = listener
656 .local_addr()
657 .expect("unexpected missing local addr for open listener");
658 info!("Opened Zcash protocol endpoint at {}", local_addr);
659
660 (listener, local_addr)
661}
662
663/// Listens for peer connections on `addr`, then sets up each connection as a
664/// Zcash peer.
665///
666/// Uses `handshaker` to perform a Zcash network protocol handshake, and sends
667/// the [`peer::Client`] result over `peerset_tx`.
668///
669/// Limits the number of active inbound connections based on `config`,
670/// and waits `min_inbound_peer_connection_interval` between connections.
671#[instrument(skip(config, listener, handshaker, peerset_tx), fields(listener_addr = ?listener.local_addr()))]
672async fn accept_inbound_connections<S>(
673 config: Config,
674 listener: TcpListener,
675 min_inbound_peer_connection_interval: Duration,
676 handshaker: S,
677 peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
678 bans_receiver: watch::Receiver<BanList>,
679 zcashd_compat_peer_ips: Vec<IpAddr>,
680) -> Result<(), BoxError>
681where
682 S: Service<peer::HandshakeRequest<TcpStream>, Response = peer::Client, Error = BoxError>
683 + Clone,
684 S::Future: Send + 'static,
685{
686 let mut recent_inbound_connections =
687 recent_by_ip::RecentByIp::new(None, Some(config.max_connections_per_ip));
688
689 let mut active_inbound_connections = ActiveConnectionCounter::new_counter_with(
690 config.peerset_inbound_connection_limit(),
691 "Inbound Connections",
692 );
693 let zcashd_compat_peer_ips: HashSet<_> = zcashd_compat_peer_ips
694 .into_iter()
695 .map(|ip| canonical_socket_addr(SocketAddr::new(ip, 0)).ip())
696 .collect();
697 // Reserve one inbound slot per configured sidecar IP, always leaving at
698 // least one public slot. Reserved slots are a fast path, not a cap:
699 // when they are taken, further connections from listed IPs compete for
700 // public slots like any other peer, so an unrelated local process (or a
701 // sidecar reconnecting before its dead connection is noticed) can never
702 // lock the real sidecar out of the node.
703 let zcashd_compat_reserved_slots = zcashd_compat_peer_ips
704 .len()
705 .min(config.peerset_inbound_connection_limit().saturating_sub(1));
706 let mut active_zcashd_compat_connections = ActiveConnectionCounter::new_counter_with(
707 zcashd_compat_reserved_slots,
708 "zcashd-compat Inbound Connections",
709 );
710 let public_inbound_connection_limit = config
711 .peerset_inbound_connection_limit()
712 .saturating_sub(zcashd_compat_reserved_slots);
713
714 // Dropping the listener also aborts unfinished handshakes.
715 let mut handshakes = JoinSet::new();
716
717 loop {
718 // Check for panics in finished tasks, before accepting new connections
719 let inbound_result = tokio::select! {
720 biased;
721 next_handshake_res = handshakes.join_next(), if !handshakes.is_empty() => {
722 // The task has already sent the peer change to the peer set.
723 let _ = next_handshake_res
724 .expect("the handshake set is not empty")
725 .panic_if_task_has_panicked();
726 continue;
727 },
728
729 // This future must wait until new connections are available: it can't have a timeout.
730 inbound_result = listener.accept() => inbound_result,
731 };
732
733 if let Ok((tcp_stream, addr)) = inbound_result {
734 // # Security
735 //
736 // Canonicalize the accepted address before it is used as a key. On a
737 // dual-stack listener an IPv4 peer connects as IPv4-mapped IPv6, but
738 // bans and misbehaviour updates are keyed on the canonical IPv4 address.
739 // This address also becomes the peer set key and the per-IP limiter key
740 // below, so canonicalizing once here keeps all of them in the same form.
741 let addr: PeerSocketAddr = canonical_peer_addr(addr);
742 record_connection_attempt_started(&config.network, ConnectionDirection::Inbound, addr);
743
744 // # Security
745 //
746 // Bans are keyed by peer group, which unmaps IPv4-mapped IPv6 and
747 // masks IPv6 to its `/64`. So a peer cannot dodge a ban — and then
748 // be granted the zcashd-compat sidecar privileges below — by
749 // connecting as IPv4-mapped IPv6 on a dual-stack listener, or from
750 // another address in a banned `/64`.
751 let canonical_ip = canonical_socket_addr(addr.remove_socket_addr_privacy()).ip();
752 let bans = bans_receiver.borrow().clone();
753 if bans.is_banned(addr.ip()) {
754 debug!(?addr, "banned inbound connection attempt");
755 record_inbound_connection_rejected(&config.network, addr, "banned");
756 std::mem::drop(tcp_stream);
757 continue;
758 }
759
760 let active_public_inbound_connections = active_inbound_connections.update_count();
761 let active_zcashd_compat_inbound_connections =
762 active_zcashd_compat_connections.update_count();
763 let active_total_inbound_connections =
764 active_public_inbound_connections + active_zcashd_compat_inbound_connections;
765 let is_zcashd_compat_peer = zcashd_compat_peer_ips.contains(&canonical_ip);
766
767 // The peer already opened a connection to us.
768 // So we want to increment the connection count as soon as possible.
769 //
770 // Sidecar-listed IPs use a reserved slot when one is free, and
771 // otherwise fall back to competing for a public slot (including
772 // the recent-IP rate limit), so a taken reserved slot delays a
773 // sidecar rather than locking it out.
774 let use_reserved_slot = is_zcashd_compat_peer
775 && active_zcashd_compat_inbound_connections < zcashd_compat_reserved_slots
776 && active_total_inbound_connections < config.peerset_inbound_connection_limit();
777
778 let connection_tracker = if use_reserved_slot {
779 active_zcashd_compat_connections.track_connection()
780 } else if active_public_inbound_connections >= public_inbound_connection_limit
781 || active_total_inbound_connections >= config.peerset_inbound_connection_limit()
782 || recent_inbound_connections.is_past_limit_or_add(addr.ip())
783 {
784 // Too many open inbound connections or pending handshakes already.
785 // Close the connection.
786 record_inbound_connection_rejected(
787 &config.network,
788 addr,
789 "capacity_or_rate_limited",
790 );
791 std::mem::drop(tcp_stream);
792 // Allow invalid connections to be cleared quickly,
793 // but still put a limit on our CPU and network usage from failed connections.
794 tokio::time::sleep(constants::MIN_INBOUND_PEER_FAILED_CONNECTION_INTERVAL).await;
795 continue;
796 } else {
797 active_inbound_connections.track_connection()
798 };
799 debug!(
800 inbound_connections = ?active_total_inbound_connections,
801 ?is_zcashd_compat_peer,
802 "handshaking on an open inbound peer connection"
803 );
804
805 accept_inbound_handshake(
806 config.network.clone(),
807 addr,
808 handshaker.clone(),
809 tcp_stream,
810 connection_tracker,
811 peerset_tx.clone(),
812 &mut handshakes,
813 )
814 .await?;
815
816 // Rate-limit inbound connection handshakes.
817 // But sleep longer after a successful connection,
818 // so we can clear out failed connections at a higher rate.
819 //
820 // If there is a flood of connections,
821 // this stops Zebra overloading the network with handshake data.
822 //
823 // Zebra can't control how many queued connections are waiting,
824 // but most OSes also limit the number of queued inbound connections on a listener port.
825 tokio::time::sleep(min_inbound_peer_connection_interval).await;
826 } else {
827 // Allow invalid connections to be cleared quickly,
828 // but still put a limit on our CPU and network usage from failed connections.
829 debug!(?inbound_result, "error accepting inbound connection");
830 tokio::time::sleep(constants::MIN_INBOUND_PEER_FAILED_CONNECTION_INTERVAL).await;
831 }
832
833 // Security: Let other tasks run after each connection is processed.
834 //
835 // Avoids remote peers starving other Zebra tasks using inbound connection successes or
836 // errors.
837 //
838 // Preventing a denial of service is important in this code, so we want to sleep *and* make
839 // the next connection after other tasks have run. (Sleeps are not guaranteed to do that.)
840 tokio::task::yield_now().await;
841 }
842}
843
844/// Set up a new inbound connection as a Zcash peer.
845///
846/// Uses `handshaker` to perform a Zcash network protocol handshake, and sends
847/// the [`peer::Client`] result over `peerset_tx`.
848//
849// TODO: when we support inbound proxies, distinguish between proxied listeners and
850// direct listeners in the span generated by this instrument macro
851#[instrument(skip(handshaker, tcp_stream, connection_tracker, peerset_tx))]
852async fn accept_inbound_handshake<S>(
853 network: Network,
854 addr: PeerSocketAddr,
855 mut handshaker: S,
856 tcp_stream: TcpStream,
857 connection_tracker: ConnectionTracker,
858 peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
859 handshakes: &mut JoinSet<()>,
860) -> Result<(), BoxError>
861where
862 S: Service<peer::HandshakeRequest<TcpStream>, Response = peer::Client, Error = BoxError>
863 + Clone,
864 S::Future: Send + 'static,
865{
866 let connected_addr = peer::ConnectedAddr::new_inbound_direct(addr);
867
868 debug!("got incoming connection");
869
870 // # Correctness
871 //
872 // Holding the drop guard returned by Span::enter across .await points will
873 // result in incorrect traces if it yields.
874 //
875 // This await is okay because the handshaker's `poll_ready` method always returns Ready.
876 handshaker.ready().await?;
877
878 // Construct a handshake future but do not drive it yet....
879 let handshake = handshaker.call(HandshakeRequest {
880 data_stream: tcp_stream,
881 connected_addr,
882 connection_tracker,
883 });
884 // ... instead, spawn a new task to handle this connection
885 let mut peerset_tx = peerset_tx.clone();
886
887 handshakes.spawn(
888 async move {
889 let handshake_result = handshake.await;
890 record_connection_attempt_finished(
891 &network,
892 ConnectionDirection::Inbound,
893 addr,
894 handshake_result.as_ref().err(),
895 );
896
897 if let Ok(client) = handshake_result {
898 // The connection limit makes sure this send doesn't block
899 let _ = peerset_tx.send((addr, client)).await;
900 } else {
901 debug!(?handshake_result, "error handshaking with inbound peer");
902 }
903 }
904 .in_current_span(),
905 );
906
907 Ok(())
908}
909
910/// An action that the peer crawler can take.
911enum CrawlerAction {
912 /// Drop the demand signal because there are too many pending handshakes.
913 DemandDrop,
914 /// Initiate a handshake to the next candidate peer in response to demand.
915 ///
916 /// If there are no available candidates, crawl existing peers.
917 DemandHandshakeOrCrawl,
918 /// Crawl existing peers for more peers, and queue dial attempts to fill
919 /// any spare outbound connection capacity, in response to a timer `tick`.
920 TimerCrawl { tick: Instant },
921 /// Clear a finished handshake.
922 HandshakeFinished,
923 /// Clear a finished demand crawl (DemandHandshakeOrCrawl with no peers).
924 DemandCrawlFinished,
925 /// Clear a finished TimerCrawl.
926 TimerCrawlFinished,
927}
928
929/// Given a channel `demand_rx` that signals a need for new peers, try to find
930/// and connect to new peers, and send the resulting `peer::Client`s through the
931/// `peerset_tx` channel.
932///
933/// Crawl for new peers every `config.crawl_new_peer_interval`.
934/// Also crawl whenever there is demand, but no new candidate peers.
935/// After crawling, try to connect to one new peer using `outbound_connector`.
936///
937/// On every crawl timer tick, also queue a dial attempt for each spare outbound
938/// connection slot that has a candidate ready to dial, so the peer set keeps
939/// growing until it reaches `config.peerset_outbound_connection_limit()` or
940/// runs out of ready candidates.
941///
942/// If a handshake fails, restore the unused demand signal by sending it to
943/// `demand_tx`.
944///
945/// The crawler terminates when a crawl or `peerset_tx` returns a
946/// permanent internal error. Transient errors and individual peer errors should
947/// be handled within the crawler.
948///
949/// Uses `active_outbound_connections` to limit the number of active outbound connections
950/// across both the initial peers and crawler. The limit is based on `config`.
951#[allow(clippy::too_many_arguments)]
952#[instrument(
953 skip(
954 config,
955 demand_tx,
956 demand_rx,
957 next_peer_service,
958 crawl_service,
959 address_book_service,
960 outbound_connector,
961 peerset_tx,
962 active_outbound_connections,
963 address_book_updater,
964 ),
965 fields(
966 new_peer_interval = ?config.crawl_new_peer_interval,
967 )
968)]
969async fn crawl_and_dial<C, S>(
970 config: Config,
971 demand_tx: futures::channel::mpsc::Sender<MorePeers>,
972 mut demand_rx: futures::channel::mpsc::Receiver<MorePeers>,
973 next_peer_service: NextPeerService,
974 crawl_service: CrawlService<S>,
975 address_book_service: AddressBookService,
976 outbound_connector: C,
977 peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
978 mut active_outbound_connections: ActiveConnectionCounter,
979 address_book_updater: AddressBookChangeSender,
980) -> Result<(), BoxError>
981where
982 C: Service<
983 OutboundConnectorRequest,
984 Response = (PeerSocketAddr, peer::Client),
985 Error = BoxError,
986 > + Clone
987 + Send
988 + 'static,
989 C::Future: Send + 'static,
990 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
991 S::Future: Send + 'static,
992{
993 use CrawlerAction::*;
994
995 info!(
996 crawl_new_peer_interval = ?config.crawl_new_peer_interval,
997 outbound_connections = ?active_outbound_connections.update_count(),
998 "starting the peer address crawler",
999 );
1000
1001 // # Concurrency
1002 //
1003 // The candidate selection and crawl services are cheap cloneable handles,
1004 // sharing their rate limits across clones. So tasks using them can be
1005 // spawned and run concurrently, without any locking. Previously, Zebra
1006 // has had deadlocks and long hangs caused by running dependent candidate
1007 // set futures in the same async task.
1008
1009 // Dropping the crawler also aborts unfinished crawls and outbound handshakes.
1010 let mut handshakes = JoinSet::new();
1011
1012 let mut crawl_timer = tokio::time::interval(config.crawl_new_peer_interval);
1013 // If the crawl is delayed, also delay all future crawls.
1014 // (Shorter intervals just add load, without any benefit.)
1015 crawl_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
1016
1017 let mut crawl_timer = IntervalStream::new(crawl_timer).map(|tick| TimerCrawl { tick });
1018
1019 // # Concurrency
1020 //
1021 // To avoid hangs and starvation, the crawler must spawn a separate task for each crawl
1022 // and handshake, so they can make progress independently (and avoid deadlocking each other).
1023 loop {
1024 metrics::gauge!(
1025 "crawler.in_flight_handshakes",
1026 "network" => network_kind_label(&config.network),
1027 )
1028 // Precision loss only affects this diagnostic count; usize values fit within f64's range.
1029 .set(handshakes.len() as f64);
1030
1031 let crawler_action = tokio::select! {
1032 biased;
1033 // Check for completed handshakes first, because the rest of the app needs them.
1034 // Pending handshakes are limited by the connection limit.
1035 next_handshake_res = handshakes.join_next(), if !handshakes.is_empty() => {
1036 match next_handshake_res
1037 .expect("the handshake set is not empty")
1038 .panic_if_task_has_panicked()
1039 {
1040 Ok(action) => action,
1041 Err(_cancelled) => continue,
1042 }
1043 },
1044 // The timer is rate-limited
1045 next_timer = crawl_timer.next() => Ok(next_timer.expect("timers never terminate")),
1046 // Turn any new demand into an action, based on the crawler's current state.
1047 //
1048 // # Concurrency
1049 //
1050 // Demand is potentially unlimited, so it must go last in a biased select!.
1051 next_demand = demand_rx.next() => next_demand.ok_or("demand stream closed, is Zebra shutting down?".into()).map(|MorePeers|{
1052 if active_outbound_connections.update_count() >= config.peerset_outbound_connection_limit() {
1053 // Too many open outbound connections or pending handshakes already
1054 DemandDrop
1055 } else {
1056 DemandHandshakeOrCrawl
1057 }
1058 })
1059 };
1060
1061 match crawler_action {
1062 // Dummy actions
1063 Ok(DemandDrop) => {
1064 // This is set to trace level because when the peerset is
1065 // congested it can generate a lot of demand signal very rapidly.
1066 trace!("too many open connections or in-flight handshakes, dropping demand signal");
1067 }
1068
1069 // Spawned tasks
1070 Ok(DemandHandshakeOrCrawl) => {
1071 let mut next_peer_service = next_peer_service.clone();
1072 let crawl_service = crawl_service.clone();
1073 let outbound_connector = outbound_connector.clone();
1074 let peerset_tx = peerset_tx.clone();
1075 let address_book_updater = address_book_updater.clone();
1076 let demand_tx = demand_tx.clone();
1077 let network = config.network.clone();
1078
1079 // Increment the connection count before we spawn the connection.
1080 let outbound_connection_tracker = active_outbound_connections.track_connection();
1081 let outbound_connections = active_outbound_connections.update_count();
1082 debug!(?outbound_connections, "opening an outbound peer connection");
1083
1084 // Spawn each handshake or crawl into an independent task, so handshakes can make
1085 // progress while crawls are running.
1086 //
1087 // # Concurrency
1088 //
1089 // The peer crawler must be able to make progress even if some handshakes are
1090 // rate-limited. So the next peer pacing is awaited inside the spawned task.
1091 handshakes.spawn(
1092 async move {
1093 // Try to get the next available peer for a handshake.
1094 //
1095 // The next peer request is served by the address book updater
1096 // task, so it shouldn't hang.
1097 let candidate = next_reconnect_peer(&mut next_peer_service).await;
1098
1099 if let Some(candidate) = candidate {
1100 // we don't need to spawn here, because there's nothing running concurrently
1101 dial(
1102 network,
1103 candidate,
1104 outbound_connector,
1105 outbound_connection_tracker,
1106 outbound_connections,
1107 peerset_tx,
1108 address_book_updater,
1109 demand_tx,
1110 )
1111 .await?;
1112
1113 Ok(HandshakeFinished)
1114 } else {
1115 // There weren't any peers, so try to get more peers.
1116 debug!("demand for peers but no available candidates");
1117
1118 crawl(crawl_service, demand_tx).await?;
1119
1120 Ok(DemandCrawlFinished)
1121 }
1122 }
1123 .in_current_span(),
1124 );
1125 }
1126 Ok(TimerCrawl { tick }) => {
1127 let crawl_service = crawl_service.clone();
1128 let address_book_service = address_book_service.clone();
1129 let crawl_demand_tx = demand_tx.clone();
1130 let spare_capacity = config
1131 .peerset_outbound_connection_limit()
1132 .saturating_sub(active_outbound_connections.update_count());
1133
1134 handshakes.spawn(
1135 async move {
1136 debug!(
1137 ?tick,
1138 spare_capacity,
1139 "crawling for more peers in response to the crawl timer"
1140 );
1141
1142 // Queue a dial attempt for each free outbound slot that has a
1143 // candidate ready to dial, so the crawler keeps trying to fill
1144 // the peer set up to the outbound connection limit.
1145 //
1146 // Capping the queued attempts at the ready candidate count avoids
1147 // spawning fallback crawls for demand that can't be met. The
1148 // demand handler re-checks the connection limit before each
1149 // handshake, so excess signals are dropped, and failed handshakes
1150 // restore their demand signal.
1151 let ready_candidates = ready_peer_count(&address_book_service).await;
1152 let mut fill_demand_tx = crawl_demand_tx.clone();
1153 for _ in 0..spare_capacity.min(ready_candidates) {
1154 if fill_demand_tx.try_send(MorePeers).is_err() {
1155 break;
1156 }
1157 }
1158
1159 crawl(crawl_service, crawl_demand_tx).await?;
1160
1161 Ok(TimerCrawlFinished)
1162 }
1163 .in_current_span(),
1164 );
1165 }
1166
1167 // Completed spawned tasks
1168 Ok(HandshakeFinished) => {
1169 // Already logged in dial()
1170 }
1171 Ok(DemandCrawlFinished) => {
1172 // This is set to trace level because when the peerset is
1173 // congested it can generate a lot of demand signal very rapidly.
1174 trace!("demand-based crawl finished");
1175 }
1176 Ok(TimerCrawlFinished) => {
1177 debug!("timer-based crawl finished");
1178 }
1179
1180 // Fatal errors and shutdowns
1181 Err(error) => {
1182 info!(?error, "crawler task exiting due to an error");
1183 return Err(error);
1184 }
1185 }
1186
1187 // Security: Let other tasks run after each crawler action is processed.
1188 //
1189 // Avoids remote peers starving other Zebra tasks using outbound connection errors.
1190 tokio::task::yield_now().await;
1191 }
1192}
1193
1194/// Try to get more peers using `crawl_service`, then queue a connection attempt using `demand_tx`.
1195/// If there were no new peers, the connection attempt is skipped.
1196#[instrument(skip(crawl_service, demand_tx))]
1197async fn crawl<S>(
1198 mut crawl_service: CrawlService<S>,
1199 mut demand_tx: futures::channel::mpsc::Sender<MorePeers>,
1200) -> Result<(), BoxError>
1201where
1202 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
1203 S::Future: Send + 'static,
1204{
1205 // Crawls have timeouts, and are served without locks,
1206 // so this call shouldn't hang.
1207 let more_peers = match crawl_once(&mut crawl_service, None).await {
1208 Ok(more_peers) => more_peers,
1209 Err(e) => {
1210 info!(
1211 ?e,
1212 "crawl service returned an error, is Zebra shutting down?"
1213 );
1214 return Err(e);
1215 }
1216 };
1217
1218 // If we got more peers, try to connect to a new peer on our next loop.
1219 //
1220 // # Security
1221 //
1222 // Crawls are rate-limited by the crawl service,
1223 // and we only try peers if a crawl actually ran.
1224 //
1225 // So if all peers have had a recent attempt, and there was recent crawl
1226 // with no peers, the channel will drain. This prevents useless crawl attempt
1227 // loops.
1228 if let Some(more_peers) = more_peers {
1229 if let Err(send_error) = demand_tx.try_send(more_peers) {
1230 if send_error.is_disconnected() {
1231 // Zebra is shutting down
1232 return Err(send_error.into());
1233 }
1234 }
1235 }
1236
1237 Ok(())
1238}
1239
1240/// Try to connect to `candidate` using `outbound_connector`.
1241/// Uses `outbound_connection_tracker` to track the active connection count.
1242///
1243/// On success, sends peers to `peerset_tx`.
1244/// On failure, marks the peer as failed in the address book,
1245/// then re-adds demand to `demand_tx`.
1246#[allow(clippy::too_many_arguments)]
1247#[instrument(skip(
1248 outbound_connector,
1249 outbound_connection_tracker,
1250 outbound_connections,
1251 peerset_tx,
1252 address_book_updater,
1253 demand_tx
1254))]
1255async fn dial<C>(
1256 network: Network,
1257 candidate: MetaAddr,
1258 mut outbound_connector: C,
1259 outbound_connection_tracker: ConnectionTracker,
1260 outbound_connections: usize,
1261 mut peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
1262 address_book_updater: AddressBookChangeSender,
1263 mut demand_tx: futures::channel::mpsc::Sender<MorePeers>,
1264) -> Result<(), BoxError>
1265where
1266 C: Service<
1267 OutboundConnectorRequest,
1268 Response = (PeerSocketAddr, peer::Client),
1269 Error = BoxError,
1270 > + Clone
1271 + Send
1272 + 'static,
1273 C::Future: Send + 'static,
1274{
1275 // If Zebra only has a few connections, we log connection failures at info level,
1276 // so users can diagnose and fix the problem. This defines the threshold for info logs.
1277 const MAX_CONNECTIONS_FOR_INFO_LOG: usize = 5;
1278
1279 // # Correctness
1280 //
1281 // To avoid hangs, the dialer must only await:
1282 // - functions that return immediately, or
1283 // - functions that have a reasonable timeout
1284
1285 debug!(?candidate.addr, "attempting outbound connection in response to demand");
1286
1287 // the connector is always ready, so this can't hang
1288 let outbound_connector = outbound_connector.ready().await?;
1289
1290 let req = OutboundConnectorRequest {
1291 addr: candidate.addr,
1292 connection_tracker: outbound_connection_tracker,
1293 };
1294
1295 // the handshake has timeouts, so it shouldn't hang
1296 record_connection_attempt_started(&network, ConnectionDirection::Outbound, candidate.addr);
1297 let handshake_result: Result<(PeerSocketAddr, peer::Client), BoxError> =
1298 outbound_connector.call(req).map(Into::into).await;
1299 record_connection_attempt_finished(
1300 &network,
1301 ConnectionDirection::Outbound,
1302 candidate.addr,
1303 handshake_result.as_ref().err(),
1304 );
1305
1306 match handshake_result {
1307 Ok((address, client)) => {
1308 debug!(?candidate.addr, "successfully dialed new peer");
1309
1310 // The connection limit makes sure this send doesn't block.
1311 peerset_tx.send((address, client)).await?;
1312 }
1313 // The connection was never opened, or it failed the handshake and was dropped.
1314 Err(error) => {
1315 // Silence verbose info logs in production, but keep logs if the number of connections is low.
1316 // Also silence them completely in tests.
1317 if outbound_connections <= MAX_CONNECTIONS_FOR_INFO_LOG && !cfg!(test) {
1318 info!(?error, ?candidate.addr, "failed to make outbound connection to peer");
1319 } else {
1320 debug!(?error, ?candidate.addr, "failed to make outbound connection to peer");
1321 }
1322 report_failed(address_book_updater.clone(), candidate).await;
1323
1324 // The demand signal that was taken out of the queue to attempt to connect to the
1325 // failed candidate never turned into a connection, so add it back.
1326 //
1327 // # Security
1328 //
1329 // Handshake failures are rate-limited by peer attempt timeouts.
1330 if let Err(send_error) = demand_tx.try_send(MorePeers) {
1331 if send_error.is_disconnected() {
1332 // Zebra is shutting down
1333 return Err(send_error.into());
1334 }
1335 }
1336 }
1337 }
1338
1339 Ok(())
1340}
1341
1342/// Mark `addr` as a failed peer to `address_book_updater`.
1343#[instrument(skip(address_book_updater))]
1344async fn report_failed(address_book_updater: AddressBookChangeSender, addr: MetaAddr) {
1345 // The connection info is the same as what's already in the address book.
1346 let addr = MetaAddr::new_errored(addr.addr, None);
1347
1348 // Ignore send errors on Zebra shutdown.
1349 let _ = address_book_updater.send(addr).await;
1350}