zebra_network/peer/handshake.rs
1//! Initial [`Handshake`]s with Zebra peers over a `PeerTransport`.
2
3use std::{
4 cmp::min,
5 fmt,
6 future::Future,
7 net::{Ipv4Addr, SocketAddr},
8 panic,
9 pin::Pin,
10 sync::Arc,
11 task::{Context, Poll},
12 time::Duration,
13};
14
15use chrono::{TimeZone, Utc};
16use futures::{channel::oneshot, future, pin_mut, FutureExt, SinkExt, StreamExt};
17use indexmap::IndexSet;
18use tokio::{
19 io::{AsyncRead, AsyncWrite},
20 sync::broadcast,
21 task::JoinError,
22 time::{error, timeout, Instant},
23};
24use tokio_stream::wrappers::IntervalStream;
25use tokio_util::codec::Framed;
26use tower::Service;
27use tracing::{span, Level, Span};
28use tracing_futures::Instrument;
29
30use zebra_chain::{
31 block,
32 chain_tip::{ChainTip, NoChainTip},
33 parameters::Network,
34 serialization::{DateTime32, SerializationError},
35};
36
37use crate::{
38 connection_metrics::RemoteVersionOutcomeGuard,
39 constants,
40 meta_addr::MetaAddrChange,
41 peer::{
42 CancelHeartbeatTask, Client, ClientRequest, Connection, ErrorSlot, HandshakeError,
43 MinimumPeerVersion, PeerError,
44 },
45 peer_set::{ConnectionTracker, InventoryChange},
46 protocol::{
47 external::{types::*, AddrInVersion, Codec, InventoryHash, Message},
48 internal::{Request, Response},
49 },
50 types::MetaAddr,
51 BoxError, Config, PeerSocketAddr, VersionMessage,
52};
53
54#[cfg(test)]
55mod tests;
56
57/// A [`Service`] that handshakes with a remote peer and constructs a
58/// client/server pair.
59///
60/// CORRECTNESS
61///
62/// To avoid hangs, each handshake (or its connector) should be:
63/// - launched in a separate task, and
64/// - wrapped in a timeout.
65pub struct Handshake<S, C = NoChainTip>
66where
67 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
68 S::Future: Send,
69 C: ChainTip + Clone + Send + 'static,
70{
71 config: Config,
72 user_agent: String,
73 our_services: PeerServices,
74 relay: bool,
75
76 inbound_service: S,
77 address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
78 inv_collector: broadcast::Sender<InventoryChange>,
79 minimum_peer_version: MinimumPeerVersion<C>,
80 nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
81
82 parent_span: Span,
83}
84
85impl<S, C> fmt::Debug for Handshake<S, C>
86where
87 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
88 S::Future: Send,
89 C: ChainTip + Clone + Send + 'static,
90{
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 // skip the channels, they don't tell us anything useful
93 f.debug_struct(std::any::type_name::<Handshake<S, C>>())
94 .field("config", &self.config)
95 .field("user_agent", &self.user_agent)
96 .field("our_services", &self.our_services)
97 .field("relay", &self.relay)
98 .field("minimum_peer_version", &self.minimum_peer_version)
99 .field("parent_span", &self.parent_span)
100 .finish()
101 }
102}
103
104impl<S, C> Clone for Handshake<S, C>
105where
106 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
107 S::Future: Send,
108 C: ChainTip + Clone + Send + 'static,
109{
110 fn clone(&self) -> Self {
111 Self {
112 config: self.config.clone(),
113 user_agent: self.user_agent.clone(),
114 our_services: self.our_services,
115 relay: self.relay,
116 inbound_service: self.inbound_service.clone(),
117 address_book_updater: self.address_book_updater.clone(),
118 inv_collector: self.inv_collector.clone(),
119 minimum_peer_version: self.minimum_peer_version.clone(),
120 nonces: self.nonces.clone(),
121 parent_span: self.parent_span.clone(),
122 }
123 }
124}
125
126/// The metadata for a peer connection.
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct ConnectionInfo {
129 /// The connected peer address, if known.
130 /// This address might not be valid for outbound connections.
131 ///
132 /// Peers can be connected via a transient inbound or proxy address,
133 /// which will appear as the connected address to the OS and Zebra.
134 pub connected_addr: ConnectedAddr,
135
136 /// The network protocol [`VersionMessage`] sent by the remote peer.
137 pub remote: VersionMessage,
138
139 /// The network protocol version negotiated with the remote peer.
140 ///
141 /// Derived from `remote.version` and the
142 /// [current `zebra_network` protocol version](constants::CURRENT_NETWORK_PROTOCOL_VERSION).
143 pub negotiated_version: Version,
144}
145
146/// The peer address that we are handshaking with.
147///
148/// Typically, we can rely on outbound addresses, but inbound addresses don't
149/// give us enough information to reconnect to that peer.
150#[derive(Copy, Clone, PartialEq, Eq)]
151pub enum ConnectedAddr {
152 /// The address we used to make a direct outbound connection.
153 ///
154 /// In an honest network, a Zcash peer is listening on this exact address
155 /// and port.
156 OutboundDirect {
157 /// The connected outbound remote address and port.
158 addr: PeerSocketAddr,
159 },
160
161 /// The address we received from the OS, when a remote peer directly
162 /// connected to our Zcash listener port.
163 ///
164 /// In an honest network, a Zcash peer might be listening on this address,
165 /// if its outbound address is the same as its listener address. But the port
166 /// is an ephemeral outbound TCP port, not a listener port.
167 InboundDirect {
168 /// The connected inbound remote address and ephemeral port.
169 ///
170 /// The IP address might be the address of a Zcash peer, but the port is an ephemeral port.
171 addr: PeerSocketAddr,
172 },
173
174 /// The proxy address we used to make an outbound connection.
175 ///
176 /// The proxy address can be used by many connections, but our own ephemeral
177 /// outbound address and port can be used as an identifier for the duration
178 /// of this connection.
179 OutboundProxy {
180 /// The remote address and port of the proxy.
181 proxy_addr: SocketAddr,
182
183 /// The local address and transient port we used to connect to the proxy.
184 transient_local_addr: SocketAddr,
185 },
186
187 /// The address we received from the OS, when a remote peer connected via an
188 /// inbound proxy.
189 ///
190 /// The proxy's ephemeral outbound address can be used as an identifier for
191 /// the duration of this connection.
192 InboundProxy {
193 /// The local address and transient port we used to connect to the proxy.
194 transient_addr: SocketAddr,
195 },
196
197 /// An isolated connection, where we deliberately don't have any connection metadata.
198 Isolated,
199 //
200 // TODO: handle Tor onion addresses
201}
202
203/// Get an unspecified IPv4 address for `network`
204pub fn get_unspecified_ipv4_addr(network: Network) -> SocketAddr {
205 (Ipv4Addr::UNSPECIFIED, network.default_port()).into()
206}
207
208use ConnectedAddr::*;
209
210impl ConnectedAddr {
211 /// Returns a new outbound directly connected addr.
212 pub fn new_outbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
213 OutboundDirect { addr }
214 }
215
216 /// Returns a new inbound directly connected addr.
217 pub fn new_inbound_direct(addr: PeerSocketAddr) -> ConnectedAddr {
218 InboundDirect { addr }
219 }
220
221 /// Returns a new outbound connected addr via `proxy`.
222 ///
223 /// `local_addr` is the ephemeral local address of the connection.
224 #[allow(unused)]
225 pub fn new_outbound_proxy(proxy: SocketAddr, local_addr: SocketAddr) -> ConnectedAddr {
226 OutboundProxy {
227 proxy_addr: proxy,
228 transient_local_addr: local_addr,
229 }
230 }
231
232 /// Returns a new inbound connected addr from `proxy`.
233 //
234 // TODO: distinguish between direct listeners and proxy listeners in the
235 // rest of zebra-network
236 #[allow(unused)]
237 pub fn new_inbound_proxy(proxy: SocketAddr) -> ConnectedAddr {
238 InboundProxy {
239 transient_addr: proxy,
240 }
241 }
242
243 /// Returns a new isolated connected addr, with no metadata.
244 pub fn new_isolated() -> ConnectedAddr {
245 Isolated
246 }
247
248 /// Returns a `PeerSocketAddr` that can be used to track this connection in the
249 /// `AddressBook`.
250 ///
251 /// `None` for inbound connections, proxy connections, and isolated
252 /// connections.
253 ///
254 /// # Correctness
255 ///
256 /// This address can be used for reconnection attempts, or as a permanent
257 /// identifier.
258 ///
259 /// # Security
260 ///
261 /// This address must not depend on the canonical address from the `Version`
262 /// message. Otherwise, malicious peers could interfere with other peers
263 /// `AddressBook` state.
264 ///
265 /// TODO: remove the `get_` from these methods (Rust style avoids `get` prefixes)
266 pub fn get_address_book_addr(&self) -> Option<PeerSocketAddr> {
267 match self {
268 OutboundDirect { addr } | InboundDirect { addr } => Some(*addr),
269 // TODO: consider using the canonical address of the peer to track
270 // outbound proxy connections
271 OutboundProxy { .. } | InboundProxy { .. } | Isolated => None,
272 }
273 }
274
275 /// Returns a `PeerSocketAddr` that can be used to temporarily identify a
276 /// connection.
277 ///
278 /// Isolated connections must not change Zebra's peer set or address book
279 /// state, so they do not have an identifier.
280 ///
281 /// # Correctness
282 ///
283 /// The returned address is only valid while the original connection is
284 /// open. It must not be used in the `AddressBook`, for outbound connection
285 /// attempts, or as a permanent identifier.
286 ///
287 /// # Security
288 ///
289 /// This address must not depend on the canonical address from the `Version`
290 /// message. Otherwise, malicious peers could interfere with other peers'
291 /// `PeerSet` state.
292 pub fn get_transient_addr(&self) -> Option<PeerSocketAddr> {
293 match self {
294 OutboundDirect { addr } => Some(*addr),
295 InboundDirect { addr } => Some(*addr),
296 OutboundProxy {
297 transient_local_addr,
298 ..
299 } => Some(PeerSocketAddr::from(*transient_local_addr)),
300 InboundProxy { transient_addr } => Some(PeerSocketAddr::from(*transient_addr)),
301 Isolated => None,
302 }
303 }
304
305 /// Returns the metrics label for this connection's address.
306 pub fn get_transient_addr_label(&self) -> String {
307 self.get_transient_addr()
308 .map_or_else(|| "isolated".to_string(), |addr| addr.to_string())
309 }
310
311 /// Returns a short label for the kind of connection.
312 pub fn get_short_kind_label(&self) -> &'static str {
313 match self {
314 OutboundDirect { .. } => "Out",
315 InboundDirect { .. } => "In",
316 OutboundProxy { .. } => "ProxOut",
317 InboundProxy { .. } => "ProxIn",
318 Isolated => "Isol",
319 }
320 }
321
322 /// Returns a list of alternate remote peer addresses, which can be used for
323 /// reconnection attempts.
324 ///
325 /// Uses the connected address, and the remote canonical address.
326 ///
327 /// Skips duplicates. If this is an outbound connection, also skips the
328 /// remote address that we're currently connected to.
329 pub fn get_alternate_addrs(
330 &self,
331 mut canonical_remote: PeerSocketAddr,
332 ) -> impl Iterator<Item = PeerSocketAddr> {
333 let addrs = match self {
334 OutboundDirect { addr } => {
335 // Fixup unspecified addresses and ports using known good data
336 if canonical_remote.ip().is_unspecified() {
337 canonical_remote.set_ip(addr.ip());
338 }
339 if canonical_remote.port() == 0 {
340 canonical_remote.set_port(addr.port());
341 }
342
343 // Try the canonical remote address, if it is different from the
344 // outbound address (which we already have in our address book)
345 if &canonical_remote != addr {
346 vec![canonical_remote]
347 } else {
348 // we didn't learn a new address from the handshake:
349 // it's the same as the outbound address, which is already in our address book
350 Vec::new()
351 }
352 }
353
354 InboundDirect { addr } => {
355 // Use the IP from the TCP connection, and the port the peer told us
356 let maybe_addr = SocketAddr::new(addr.ip(), canonical_remote.port()).into();
357
358 // Try both addresses, but remove one duplicate if they match
359 if canonical_remote != maybe_addr {
360 vec![canonical_remote, maybe_addr]
361 } else {
362 vec![canonical_remote]
363 }
364 }
365
366 // Proxy addresses can't be used for reconnection attempts, but we
367 // can try the canonical remote address
368 OutboundProxy { .. } | InboundProxy { .. } => vec![canonical_remote],
369
370 // Hide all metadata for isolated connections
371 Isolated => Vec::new(),
372 };
373
374 addrs.into_iter()
375 }
376
377 /// Returns true if the [`ConnectedAddr`] was created for an inbound connection.
378 pub fn is_inbound(&self) -> bool {
379 matches!(self, InboundDirect { .. } | InboundProxy { .. })
380 }
381}
382
383impl fmt::Debug for ConnectedAddr {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 let kind = self.get_short_kind_label();
386 let addr = self.get_transient_addr_label();
387
388 if matches!(self, Isolated) {
389 f.write_str(kind)
390 } else {
391 f.debug_tuple(kind).field(&addr).finish()
392 }
393 }
394}
395
396/// A builder for `Handshake`.
397pub struct Builder<S, C = NoChainTip>
398where
399 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
400 S::Future: Send,
401 C: ChainTip + Clone + Send + 'static,
402{
403 config: Option<Config>,
404 our_services: Option<PeerServices>,
405 user_agent: Option<String>,
406 relay: Option<bool>,
407
408 inbound_service: Option<S>,
409 address_book_updater: Option<tokio::sync::mpsc::Sender<MetaAddrChange>>,
410 inv_collector: Option<broadcast::Sender<InventoryChange>>,
411 latest_chain_tip: C,
412}
413
414impl<S, C> Builder<S, C>
415where
416 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
417 S::Future: Send,
418 C: ChainTip + Clone + Send + 'static,
419{
420 /// Provide a config. Mandatory.
421 pub fn with_config(mut self, config: Config) -> Self {
422 self.config = Some(config);
423 self
424 }
425
426 /// Provide a service to handle inbound requests. Mandatory.
427 pub fn with_inbound_service(mut self, inbound_service: S) -> Self {
428 self.inbound_service = Some(inbound_service);
429 self
430 }
431
432 /// Provide a channel for registering inventory advertisements. Optional.
433 ///
434 /// This channel takes transient remote addresses, which the `PeerSet` uses
435 /// to look up peers that have specific inventory.
436 pub fn with_inventory_collector(
437 mut self,
438 inv_collector: broadcast::Sender<InventoryChange>,
439 ) -> Self {
440 self.inv_collector = Some(inv_collector);
441 self
442 }
443
444 /// Provide a hook for timestamp collection. Optional.
445 ///
446 /// This channel takes `MetaAddr`s, permanent addresses which can be used to
447 /// make outbound connections to peers.
448 pub fn with_address_book_updater(
449 mut self,
450 address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
451 ) -> Self {
452 self.address_book_updater = Some(address_book_updater);
453 self
454 }
455
456 /// Provide the services this node advertises to other peers. Optional.
457 ///
458 /// If this is unset, the node will advertise itself as a client.
459 pub fn with_advertised_services(mut self, services: PeerServices) -> Self {
460 self.our_services = Some(services);
461 self
462 }
463
464 /// Provide this node's user agent. Optional.
465 ///
466 /// This must be a valid BIP14 string. If it is unset, the user-agent will be empty.
467 pub fn with_user_agent(mut self, user_agent: String) -> Self {
468 self.user_agent = Some(user_agent);
469 self
470 }
471
472 /// Provide a realtime endpoint to obtain the current best chain tip block height. Optional.
473 ///
474 /// If this is unset, the minimum accepted protocol version for peer connections is kept
475 /// constant over network upgrade activations.
476 ///
477 /// Use [`NoChainTip`] to explicitly provide no chain tip.
478 pub fn with_latest_chain_tip<NewC>(self, latest_chain_tip: NewC) -> Builder<S, NewC>
479 where
480 NewC: ChainTip + Clone + Send + 'static,
481 {
482 Builder {
483 latest_chain_tip,
484
485 // TODO: Until Rust RFC 2528 reaches stable, we can't do `..self`
486 config: self.config,
487 inbound_service: self.inbound_service,
488 address_book_updater: self.address_book_updater,
489 our_services: self.our_services,
490 user_agent: self.user_agent,
491 relay: self.relay,
492 inv_collector: self.inv_collector,
493 }
494 }
495
496 /// Whether to request that peers relay transactions to our node. Optional.
497 ///
498 /// If this is unset, the node will not request transactions.
499 pub fn want_transactions(mut self, relay: bool) -> Self {
500 self.relay = Some(relay);
501 self
502 }
503
504 /// Consume this builder and produce a [`Handshake`].
505 ///
506 /// Returns an error only if any mandatory field was unset.
507 pub fn finish(self) -> Result<Handshake<S, C>, &'static str> {
508 let config = self.config.ok_or("did not specify config")?;
509 let inbound_service = self
510 .inbound_service
511 .ok_or("did not specify inbound service")?;
512 let inv_collector = self.inv_collector.unwrap_or_else(|| {
513 let (tx, _) = broadcast::channel(100);
514 tx
515 });
516 let address_book_updater = self.address_book_updater.unwrap_or_else(|| {
517 // No `AddressBookUpdater` for timestamp collection was passed, so create a stub
518 // channel. Dropping the receiver means sends will fail, but we don't care.
519 let (tx, _rx) = tokio::sync::mpsc::channel(1);
520 tx
521 });
522 let nonces = Arc::new(futures::lock::Mutex::new(IndexSet::new()));
523 let user_agent = self.user_agent.unwrap_or_default();
524 let our_services = self.our_services.unwrap_or_else(PeerServices::empty);
525 let relay = self.relay.unwrap_or(false);
526 let network = config.network.clone();
527 let minimum_peer_version = MinimumPeerVersion::new(self.latest_chain_tip, &network);
528
529 Ok(Handshake {
530 config,
531 user_agent,
532 our_services,
533 relay,
534 inbound_service,
535 address_book_updater,
536 inv_collector,
537 minimum_peer_version,
538 nonces,
539 parent_span: Span::current(),
540 })
541 }
542}
543
544impl<S> Handshake<S, NoChainTip>
545where
546 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
547 S::Future: Send,
548{
549 /// Create a builder that configures a [`Handshake`] service.
550 pub fn builder() -> Builder<S, NoChainTip> {
551 // We don't derive `Default` because the derive inserts a `where S:
552 // Default` bound even though `Option<S>` implements `Default` even if
553 // `S` does not.
554 Builder {
555 config: None,
556 our_services: None,
557 user_agent: None,
558 relay: None,
559 inbound_service: None,
560 address_book_updater: None,
561 inv_collector: None,
562 latest_chain_tip: NoChainTip,
563 }
564 }
565}
566
567/// Negotiate the Zcash network protocol version with the remote peer at `connected_addr`, using
568/// the connection `peer_conn`.
569///
570/// We split `Handshake` into its components before calling this function, to avoid infectious
571/// `Sync` bounds on the returned future.
572///
573/// Returns the [`VersionMessage`] sent by the remote peer, and the [`Version`] negotiated with the
574/// remote peer, inside a [`ConnectionInfo`] struct.
575#[allow(clippy::too_many_arguments)]
576pub async fn negotiate_version<PeerTransport>(
577 peer_conn: &mut Framed<PeerTransport, Codec>,
578 connected_addr: &ConnectedAddr,
579 config: Config,
580 nonces: Arc<futures::lock::Mutex<IndexSet<Nonce>>>,
581 user_agent: String,
582 our_services: PeerServices,
583 relay: bool,
584 mut minimum_peer_version: MinimumPeerVersion<impl ChainTip>,
585) -> Result<Arc<ConnectionInfo>, HandshakeError>
586where
587 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
588{
589 // Create a random nonce for this connection
590 let local_nonce = Nonce::default();
591
592 // Insert the nonce for this handshake into the shared nonce set.
593 // Each connection has its own connection state, and handshakes execute concurrently.
594 //
595 // # Correctness
596 //
597 // It is ok to wait for the lock here, because handshakes have a short
598 // timeout, and the async mutex will be released when the task times
599 // out.
600 {
601 let mut locked_nonces = nonces.lock().await;
602
603 // Duplicate nonces are very rare, because they require a 64-bit random number collision,
604 // and the nonce set is limited to a few hundred entries.
605 let is_unique_nonce = locked_nonces.insert(local_nonce);
606 if !is_unique_nonce {
607 return Err(HandshakeError::LocalDuplicateNonce);
608 }
609
610 // # Security
611 //
612 // Limit the amount of memory used for nonces.
613 // Nonces can be left in the set if the connection fails or times out between
614 // the nonce being inserted, and it being removed.
615 //
616 // Zebra has strict connection limits, so we limit the number of nonces to
617 // the configured connection limit.
618 // This is a tradeoff between:
619 // - avoiding memory denial of service attacks which make large numbers of connections,
620 // for example, 100 failed inbound connections takes 1 second.
621 // - memory usage: 16 bytes per `Nonce`, 3.2 kB for 200 nonces
622 // - collision probability: two hundred 64-bit nonces have a very low collision probability
623 // <https://en.wikipedia.org/wiki/Birthday_problem#Probability_of_a_shared_birthday_(collision)>
624 while locked_nonces.len() > config.peerset_total_connection_limit() {
625 locked_nonces.shift_remove_index(0);
626 }
627
628 std::mem::drop(locked_nonces);
629 }
630
631 // Don't leak our exact clock skew to our peers. On the other hand,
632 // we can't deviate too much, or zcashd will get confused.
633 // Inspection of the zcashd source code reveals that the timestamp
634 // is only ever used at the end of parsing the version message, in
635 //
636 // pfrom->nTimeOffset = timeWarning.AddTimeData(pfrom->addr, nTime, GetTime());
637 //
638 // AddTimeData is defined in src/timedata.cpp and is a no-op as long
639 // as the difference between the specified timestamp and the
640 // zcashd's local time is less than TIMEDATA_WARNING_THRESHOLD, set
641 // to 10 * 60 seconds (10 minutes).
642 //
643 // nTimeOffset is peer metadata that is never used, except for
644 // statistics.
645 //
646 // To try to stay within the range where zcashd will ignore our clock skew,
647 // truncate the timestamp to the nearest 5 minutes.
648 let now = Utc::now().timestamp();
649 let timestamp = Utc
650 .timestamp_opt(now - now.rem_euclid(5 * 60), 0)
651 .single()
652 .expect("in-range number of seconds and valid nanosecond");
653
654 // Whether this node is still syncing, used below to decide whether outbound peers must
655 // advertise `NODE_NETWORK`. Read before the match below, which can move `config.network`.
656 let is_syncing = !minimum_peer_version
657 .chain_tip()
658 .is_at_or_near_network_tip(&config.network);
659
660 let network = config.network.clone();
661 let (their_addr, our_services, our_listen_addr) = match connected_addr {
662 // Version messages require an address, so we use
663 // an unspecified address for Isolated connections
664 Isolated => {
665 let unspec_ipv4 = get_unspecified_ipv4_addr(config.network);
666 (unspec_ipv4.into(), PeerServices::empty(), unspec_ipv4)
667 }
668 _ => {
669 let their_addr = connected_addr
670 .get_transient_addr()
671 .expect("non-Isolated connections have a remote addr");
672
673 // Include the configured external address in our version message, if any, otherwise, include our listen address.
674 let advertise_addr = match config.external_addr {
675 Some(external_addr) => {
676 info!(?their_addr, ?config.listen_addr, "using external address for Version messages");
677 external_addr
678 }
679 None => config.listen_addr,
680 };
681
682 (their_addr, our_services, advertise_addr)
683 }
684 };
685
686 let start_height = minimum_peer_version
687 .chain_tip()
688 .best_tip_height()
689 .unwrap_or(block::Height(0));
690
691 let our_version = VersionMessage {
692 version: constants::CURRENT_NETWORK_PROTOCOL_VERSION,
693 services: our_services,
694 timestamp,
695 address_recv: AddrInVersion::new(their_addr, PeerServices::NODE_NETWORK),
696 // TODO: detect external address (#1893)
697 address_from: AddrInVersion::new(our_listen_addr, our_services),
698 nonce: local_nonce,
699 user_agent: user_agent.clone(),
700 start_height,
701 relay,
702 }
703 .into();
704
705 debug!(?our_version, "sending initial version message");
706 peer_conn.send(our_version).await?;
707
708 let mut remote_msg = peer_conn
709 .next()
710 .await
711 .ok_or(HandshakeError::ConnectionClosed)??;
712
713 // Wait for next message if the one we got is not Version
714 let remote: VersionMessage = loop {
715 match remote_msg {
716 Message::Version(version_message) => {
717 debug!(?version_message, "got version message from remote peer");
718 break version_message;
719 }
720 _ => {
721 remote_msg = peer_conn
722 .next()
723 .await
724 .ok_or(HandshakeError::ConnectionClosed)??;
725 debug!(?remote_msg, "ignoring non-version message from remote peer");
726 }
727 }
728 };
729
730 let remote_address_services = remote.address_from.untrusted_services();
731 let mut remote_version_outcome =
732 RemoteVersionOutcomeGuard::new(&network, connected_addr, &remote.user_agent);
733 if remote_address_services != remote.services {
734 info!(
735 ?remote.services,
736 ?remote_address_services,
737 ?remote.user_agent,
738 "peer with inconsistent version services and version address services",
739 );
740 }
741
742 // Check for nonce reuse, indicating self-connection
743 //
744 // # Correctness
745 //
746 // We must wait for the lock before we continue with the connection, to avoid
747 // self-connection. If the connection times out, the async lock will be
748 // released.
749 //
750 // # Security
751 //
752 // We don't remove the nonce here, because peers that observe our network traffic could
753 // maliciously remove nonces, and force us to make self-connections.
754 let nonce_reuse = nonces.lock().await.contains(&remote.nonce);
755 if nonce_reuse {
756 info!(?connected_addr, "rejecting self-connection attempt");
757 return Err(remote_version_outcome.record_error(HandshakeError::RemoteNonceReuse));
758 }
759
760 // # Security
761 //
762 // Reject connections to peers on old versions, because they might not know about all
763 // network upgrades and could lead to chain forks or slower block propagation.
764 let min_version = minimum_peer_version.current();
765
766 if remote.version < min_version {
767 debug!(
768 remote_ip = ?their_addr,
769 ?remote.version,
770 ?min_version,
771 ?remote.user_agent,
772 "disconnecting from peer with obsolete network protocol version",
773 );
774
775 // the value is the number of rejected handshakes, by peer IP and protocol version
776 metrics::counter!(
777 "zcash.net.peers.obsolete",
778 "remote_ip" => their_addr.to_string(),
779 "remote_version" => remote.version.to_string(),
780 "min_version" => min_version.to_string(),
781 "user_agent" => remote.user_agent.clone(),
782 )
783 .increment(1);
784
785 // the value is the remote version of the most recent rejected handshake from each peer
786 metrics::gauge!(
787 "zcash.net.peers.version.obsolete",
788 "remote_ip" => their_addr.to_string(),
789 )
790 .set(remote.version.0 as f64);
791
792 // Disconnect if peer is using an obsolete version.
793 return Err(
794 remote_version_outcome.record_error(HandshakeError::ObsoleteVersion(remote.version))
795 );
796 }
797
798 // # Security
799 //
800 // While syncing, require `NODE_NETWORK` from outbound peers: peers without it can't serve
801 // us historic blocks, but still occupy outbound slots and receive syncer block requests.
802 // When many reachable listeners are non-serving, those slots can fill up and stall a fresh
803 // sync (#11061). This mirrors Bitcoin Core, which requires block-serving peers during
804 // initial block download.
805 //
806 // At or near the network tip the requirement is dropped, because non-serving peers (like
807 // pruned nodes) can still serve recent blocks and transactions. Inbound and isolated
808 // connections are always exempt, so light clients can still connect to us.
809 if is_syncing
810 && matches!(connected_addr, OutboundDirect { .. } | OutboundProxy { .. })
811 && !remote.services.contains(PeerServices::NODE_NETWORK)
812 {
813 debug!(
814 remote_ip = ?their_addr,
815 ?remote.services,
816 ?remote.user_agent,
817 "disconnecting from non-serving peer",
818 );
819
820 // the value is the number of rejected handshakes, by peer IP and advertised services
821 metrics::counter!(
822 "zcash.net.peers.missing_services",
823 "remote_ip" => their_addr.to_string(),
824 "remote_services" => format!("{:?}", remote.services),
825 "user_agent" => remote.user_agent.clone(),
826 )
827 .increment(1);
828
829 // Disconnect if the outbound peer doesn't advertise the required services.
830 return Err(
831 remote_version_outcome.record_error(HandshakeError::MissingRequiredServices {
832 services: remote.services,
833 }),
834 );
835 }
836
837 let negotiated_version = min(constants::CURRENT_NETWORK_PROTOCOL_VERSION, remote.version);
838
839 // Limit containing struct size, and avoid multiple duplicates of 300+ bytes of data.
840 let connection_info = Arc::new(ConnectionInfo {
841 connected_addr: *connected_addr,
842 remote,
843 negotiated_version,
844 });
845
846 debug!(
847 remote_ip = ?their_addr,
848 ?connection_info.remote.version,
849 ?negotiated_version,
850 ?min_version,
851 ?connection_info.remote.user_agent,
852 "negotiated network protocol version with peer",
853 );
854
855 // the value is the number of connected handshakes, by peer IP and protocol version
856 metrics::counter!(
857 "zcash.net.peers.connected",
858 "remote_ip" => their_addr.to_string(),
859 "remote_version" => connection_info.remote.version.to_string(),
860 "negotiated_version" => negotiated_version.to_string(),
861 "min_version" => min_version.to_string(),
862 "user_agent" => connection_info.remote.user_agent.clone(),
863 )
864 .increment(1);
865
866 // the value is the remote version of the most recent connected handshake from each peer
867 metrics::gauge!(
868 "zcash.net.peers.version.connected",
869 "remote_ip" => their_addr.to_string(),
870 )
871 .set(connection_info.remote.version.0 as f64);
872
873 if let Err(error) = peer_conn.send(Message::Verack).await {
874 return Err(remote_version_outcome.record_error(HandshakeError::from(error)));
875 }
876
877 let mut remote_msg = match peer_conn.next().await {
878 Some(Ok(message)) => message,
879 Some(Err(error)) => {
880 return Err(remote_version_outcome.record_error(HandshakeError::from(error)));
881 }
882 None => {
883 return Err(remote_version_outcome.record_error(HandshakeError::ConnectionClosed));
884 }
885 };
886
887 // Wait for next message if the one we got is not Verack
888 loop {
889 match remote_msg {
890 Message::Verack => {
891 debug!(?remote_msg, "got verack message from remote peer");
892 break;
893 }
894 _ => {
895 remote_msg = match peer_conn.next().await {
896 Some(Ok(message)) => message,
897 Some(Err(error)) => {
898 return Err(
899 remote_version_outcome.record_error(HandshakeError::from(error))
900 );
901 }
902 None => {
903 return Err(
904 remote_version_outcome.record_error(HandshakeError::ConnectionClosed)
905 );
906 }
907 };
908 debug!(?remote_msg, "ignoring non-verack message from remote peer");
909 }
910 }
911 }
912
913 remote_version_outcome.record_success();
914 Ok(connection_info)
915}
916
917/// A handshake request.
918/// Contains the information needed to handshake with the peer.
919pub struct HandshakeRequest<PeerTransport>
920where
921 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
922{
923 /// The tokio [`TcpStream`](tokio::net::TcpStream) or Tor
924 /// `arti_client::DataStream` to the peer.
925 // Use [`arti_client::DataStream`] when #5492 is done.
926 pub data_stream: PeerTransport,
927
928 /// The address of the peer, and other related information.
929 pub connected_addr: ConnectedAddr,
930
931 /// A connection tracker that reduces the open connection count when dropped.
932 ///
933 /// Used to limit the number of open connections in Zebra.
934 pub connection_tracker: ConnectionTracker,
935}
936
937impl<S, PeerTransport, C> Service<HandshakeRequest<PeerTransport>> for Handshake<S, C>
938where
939 S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + 'static,
940 S::Future: Send,
941 C: ChainTip + Clone + Send + 'static,
942 PeerTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static,
943{
944 type Response = Client;
945 type Error = BoxError;
946 type Future =
947 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
948
949 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
950 Poll::Ready(Ok(()))
951 }
952
953 fn call(&mut self, req: HandshakeRequest<PeerTransport>) -> Self::Future {
954 let HandshakeRequest {
955 data_stream,
956 connected_addr,
957 mut connection_tracker,
958 } = req;
959
960 let negotiator_span = debug_span!("negotiator", peer = ?connected_addr);
961 // set the peer connection span's parent to the global span, as it
962 // should exist independently of its creation source (inbound
963 // connection, crawler, initial peer, ...)
964 let connection_span =
965 span!(parent: &self.parent_span, Level::INFO, "", peer = ?connected_addr);
966
967 // Clone these upfront, so they can be moved into the future.
968 let nonces = self.nonces.clone();
969 let inbound_service = self.inbound_service.clone();
970 let address_book_updater = self.address_book_updater.clone();
971 let inv_collector = self.inv_collector.clone();
972 let config = self.config.clone();
973 let user_agent = self.user_agent.clone();
974 let our_services = self.our_services;
975 let relay = self.relay;
976 let minimum_peer_version = self.minimum_peer_version.clone();
977
978 // # Security
979 //
980 // `zebra_network::init()` implements a connection timeout on this future.
981 // Any code outside this future does not have a timeout.
982 let fut = async move {
983 debug!(
984 addr = ?connected_addr,
985 "negotiating protocol version with remote peer"
986 );
987
988 // Start timing the handshake for metrics
989 let handshake_start = Instant::now();
990
991 let mut peer_conn = Framed::new(
992 data_stream,
993 Codec::builder()
994 .for_network(&config.network)
995 .with_metrics_addr_label(connected_addr.get_transient_addr_label())
996 .finish(),
997 );
998
999 let connection_info = match negotiate_version(
1000 &mut peer_conn,
1001 &connected_addr,
1002 config,
1003 nonces,
1004 user_agent,
1005 our_services,
1006 relay,
1007 minimum_peer_version,
1008 )
1009 .await
1010 {
1011 Ok(info) => {
1012 // Record successful handshake duration
1013 let duration = handshake_start.elapsed().as_secs_f64();
1014 metrics::histogram!(
1015 "zcash.net.peer.handshake.duration_seconds",
1016 "result" => "success"
1017 )
1018 .record(duration);
1019 info
1020 }
1021 Err(err) => {
1022 // Record failed handshake duration and failure reason
1023 let duration = handshake_start.elapsed().as_secs_f64();
1024 let reason = match &err {
1025 HandshakeError::UnexpectedMessage(_) => "unexpected_message",
1026 HandshakeError::RemoteNonceReuse => "nonce_reuse",
1027 HandshakeError::LocalDuplicateNonce => "duplicate_nonce",
1028 HandshakeError::ConnectionClosed => "connection_closed",
1029 HandshakeError::Io(_) => "io_error",
1030 HandshakeError::Serialization(_) => "serialization",
1031 HandshakeError::ObsoleteVersion(_) => "obsolete_version",
1032 HandshakeError::MissingRequiredServices { .. } => {
1033 "missing_required_services"
1034 }
1035 HandshakeError::Timeout => "timeout",
1036 };
1037 metrics::histogram!(
1038 "zcash.net.peer.handshake.duration_seconds",
1039 "result" => "failure"
1040 )
1041 .record(duration);
1042 metrics::counter!(
1043 "zcash.net.peer.handshake.failures.total",
1044 "reason" => reason
1045 )
1046 .increment(1);
1047
1048 // Rejected non-serving peers are reported by the crawler's `report_failed`
1049 // without their services, so they get the standard failure backoff and can
1050 // be dialed again once the node is near the network tip (#11061).
1051 return Err(err);
1052 }
1053 };
1054
1055 let remote_services = connection_info.remote.services;
1056
1057 // The handshake succeeded: update the peer status from AttemptPending to Responded,
1058 // send initial connection info, and update the active connection counter.
1059 connection_tracker.mark_open();
1060 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1061 // the collector doesn't depend on network activity,
1062 // so this await should not hang
1063 let _ = address_book_updater
1064 .send(MetaAddr::new_connected(
1065 book_addr,
1066 &remote_services,
1067 connected_addr.is_inbound(),
1068 connection_info.remote.user_agent.clone(),
1069 connection_info.negotiated_version,
1070 ))
1071 .await;
1072 }
1073
1074 // Reconfigure the codec to use the negotiated version.
1075 //
1076 // TODO: The tokio documentation says not to do this while any frames are still being processed.
1077 // Since we don't know that here, another way might be to release the tcp
1078 // stream from the unversioned Framed wrapper and construct a new one with a versioned codec.
1079 let bare_codec = peer_conn.codec_mut();
1080 bare_codec.reconfigure_version(connection_info.negotiated_version);
1081 bare_codec.reconfigure_full_body_len();
1082
1083 debug!("constructing client, spawning server");
1084
1085 // These channels communicate between the inbound and outbound halves of the connection,
1086 // and between the different connection tasks. We create separate tasks and channels
1087 // for each new connection.
1088 let (server_tx, server_rx) = futures::channel::mpsc::channel(0);
1089 let (shutdown_tx, shutdown_rx) = oneshot::channel();
1090 let error_slot = ErrorSlot::default();
1091
1092 let (peer_tx, peer_rx) = peer_conn.split();
1093
1094 // Instrument the peer's rx and tx streams.
1095
1096 let inner_conn_span = connection_span.clone();
1097 let peer_tx = peer_tx.with(move |msg: Message| {
1098 let span = debug_span!(parent: inner_conn_span.clone(), "outbound_metric");
1099 // Add a metric for outbound messages.
1100 metrics::counter!(
1101 "zcash.net.out.messages",
1102 "command" => msg.command(),
1103 "addr" => connected_addr.get_transient_addr_label(),
1104 )
1105 .increment(1);
1106 // We need to use future::ready rather than an async block here,
1107 // because we need the sink to be Unpin, and the With<Fut, ...>
1108 // returned by .with is Unpin only if Fut is Unpin, and the
1109 // futures generated by async blocks are not Unpin.
1110 future::ready(Ok(msg)).instrument(span)
1111 });
1112
1113 // CORRECTNESS
1114 //
1115 // Ping/Pong messages and every error must update the peer address state via
1116 // the inbound_ts_collector.
1117 //
1118 // The heartbeat task sends regular Ping/Pong messages,
1119 // and it ends the connection if the heartbeat times out.
1120 // So we can just track peer activity based on Ping and Pong.
1121 // (This significantly improves performance, by reducing time system calls.)
1122 let inbound_ts_collector = address_book_updater.clone();
1123 let inbound_inv_collector = inv_collector.clone();
1124 let ts_inner_conn_span = connection_span.clone();
1125 let inv_inner_conn_span = connection_span.clone();
1126 let peer_rx = peer_rx
1127 .then(move |msg| {
1128 // Add a metric for inbound messages and errors.
1129 // Fire a timestamp or failure event.
1130 let inbound_ts_collector = inbound_ts_collector.clone();
1131 let span =
1132 debug_span!(parent: ts_inner_conn_span.clone(), "inbound_ts_collector");
1133
1134 async move {
1135 match &msg {
1136 Ok(msg) => {
1137 metrics::counter!(
1138 "zcash.net.in.messages",
1139 "command" => msg.command(),
1140 "addr" => connected_addr.get_transient_addr_label(),
1141 )
1142 .increment(1);
1143
1144 // # Security
1145 //
1146 // Peer messages are not rate-limited, so we can't send anything
1147 // to a shared channel or do anything expensive here.
1148 }
1149 Err(err) => {
1150 metrics::counter!(
1151 "zebra.net.in.errors",
1152 "error" => err.to_string(),
1153 "addr" => connected_addr.get_transient_addr_label(),
1154 )
1155 .increment(1);
1156
1157 // # Security
1158 //
1159 // Peer errors are rate-limited because:
1160 // - opening connections is rate-limited
1161 // - the number of connections is limited
1162 // - after the first error, the peer is disconnected
1163 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1164 let _ = inbound_ts_collector
1165 .send(MetaAddr::new_errored(book_addr, remote_services))
1166 .await;
1167 }
1168 }
1169 }
1170 msg
1171 }
1172 .instrument(span)
1173 })
1174 .then(move |msg| {
1175 let inbound_inv_collector = inbound_inv_collector.clone();
1176 let span = debug_span!(parent: inv_inner_conn_span.clone(), "inventory_filter");
1177 register_inventory_status(msg, connected_addr, inbound_inv_collector)
1178 .instrument(span)
1179 })
1180 .boxed();
1181
1182 // If we've learned potential peer addresses from the inbound connection remote address
1183 // or the handshake version message, add those addresses to the peer cache for this
1184 // peer.
1185 //
1186 // # Security
1187 //
1188 // We can't add these alternate addresses directly to the address book. If we did,
1189 // malicious peers could interfere with the address book state of other peers by
1190 // providing their addresses in `Version` messages. Or they could fill the address book
1191 // with fake addresses.
1192 //
1193 // These peer addresses are rate-limited because:
1194 // - opening connections is rate-limited
1195 // - these addresses are put in the peer address cache
1196 // - the peer address cache is only used when Zebra requests addresses from that peer
1197 let remote_canonical_addr = connection_info.remote.address_from.addr();
1198 let alternate_addrs = connected_addr
1199 .get_alternate_addrs(remote_canonical_addr)
1200 .map(|addr| {
1201 // Assume the connecting node is a server node, and it's available now.
1202 MetaAddr::new_gossiped_meta_addr(
1203 addr,
1204 PeerServices::NODE_NETWORK,
1205 DateTime32::now(),
1206 )
1207 });
1208
1209 let server = Connection::new(
1210 inbound_service,
1211 server_rx,
1212 error_slot.clone(),
1213 peer_tx,
1214 connection_tracker,
1215 connection_info.clone(),
1216 alternate_addrs.collect(),
1217 );
1218
1219 let connection_task = tokio::spawn(
1220 server
1221 .run(peer_rx)
1222 .instrument(connection_span.clone())
1223 .boxed(),
1224 );
1225
1226 let heartbeat_task = tokio::spawn(
1227 send_periodic_heartbeats_with_shutdown_handle(
1228 connected_addr,
1229 shutdown_rx,
1230 server_tx.clone(),
1231 address_book_updater.clone(),
1232 )
1233 .instrument(tracing::debug_span!(parent: connection_span, "heartbeat"))
1234 .boxed(),
1235 );
1236
1237 let client = Client {
1238 connection_info,
1239 shutdown_tx: Some(shutdown_tx),
1240 server_tx,
1241 inv_collector,
1242 error_slot,
1243 connection_task,
1244 heartbeat_task,
1245 };
1246
1247 Ok(client)
1248 };
1249
1250 // Correctness: As a defence-in-depth against hangs, wrap the entire handshake in a timeout.
1251 let fut = timeout(constants::HANDSHAKE_TIMEOUT, fut);
1252
1253 // Spawn a new task to drive this handshake, forwarding panics to the calling task.
1254 tokio::spawn(fut.instrument(negotiator_span))
1255 .map(
1256 |join_result: Result<
1257 Result<Result<Client, HandshakeError>, error::Elapsed>,
1258 JoinError,
1259 >| {
1260 match join_result {
1261 Ok(Ok(Ok(connection_client))) => Ok(connection_client),
1262 Ok(Ok(Err(handshake_error))) => Err(handshake_error.into()),
1263 Ok(Err(timeout_error)) => Err(timeout_error.into()),
1264 Err(join_error) => match join_error.try_into_panic() {
1265 // Forward panics to the calling task
1266 Ok(panic_reason) => panic::resume_unwind(panic_reason),
1267 Err(join_error) => Err(join_error.into()),
1268 },
1269 }
1270 },
1271 )
1272 .boxed()
1273 }
1274}
1275
1276/// Register any advertised or missing inventory in `msg` for `connected_addr`.
1277pub(crate) async fn register_inventory_status(
1278 msg: Result<Message, SerializationError>,
1279 connected_addr: ConnectedAddr,
1280 inv_collector: broadcast::Sender<InventoryChange>,
1281) -> Result<Message, SerializationError> {
1282 match (&msg, connected_addr.get_transient_addr()) {
1283 (Ok(Message::Inv(advertised)), Some(transient_addr)) => {
1284 // We ignore inventory messages with more than one
1285 // block, because they are most likely replies to a
1286 // query, rather than a newly gossiped block.
1287 //
1288 // (We process inventory messages with any number of
1289 // transactions.)
1290 //
1291 // https://zebra.zfnd.org/dev/rfcs/0003-inventory-tracking.html#inventory-monitoring
1292 //
1293 // Note: zcashd has a bug where it merges queued inv messages of
1294 // the same or different types. Zebra compensates by sending `notfound`
1295 // responses to the inv collector. (#2156, #1768)
1296 //
1297 // (We can't split `inv`s, because that fills the inventory registry
1298 // with useless entries that the whole network has, making it large and slow.)
1299 match advertised.as_slice() {
1300 [advertised @ InventoryHash::Block(_)] => {
1301 debug!(
1302 ?advertised,
1303 "registering gossiped advertised block inventory for peer"
1304 );
1305
1306 // The peer set and inv collector use the peer's remote
1307 // address as an identifier
1308 // If all receivers have been dropped, `send` returns an error.
1309 // When that happens, Zebra is shutting down, so we want to ignore this error.
1310 let _ = inv_collector
1311 .send(InventoryChange::new_available(*advertised, transient_addr));
1312 }
1313 advertised => {
1314 let advertised = advertised
1315 .iter()
1316 .filter(|advertised| advertised.unmined_tx_id().is_some());
1317
1318 debug!(
1319 ?advertised,
1320 "registering advertised unmined transaction inventory for peer",
1321 );
1322
1323 if let Some(change) =
1324 InventoryChange::new_available_multi(advertised, transient_addr)
1325 {
1326 // Ignore channel errors that should only happen during shutdown.
1327 let _ = inv_collector.send(change);
1328 }
1329 }
1330 }
1331 }
1332
1333 (Ok(Message::NotFound(missing)), Some(transient_addr)) => {
1334 // Ignore Errors and the unsupported FilteredBlock type
1335 let missing = missing.iter().filter(|missing| {
1336 missing.unmined_tx_id().is_some() || missing.block_hash().is_some()
1337 });
1338
1339 debug!(?missing, "registering missing inventory for peer");
1340
1341 if let Some(change) = InventoryChange::new_missing_multi(missing, transient_addr) {
1342 let _ = inv_collector.send(change);
1343 }
1344 }
1345 _ => {}
1346 }
1347
1348 msg
1349}
1350
1351/// Send periodical heartbeats to `server_tx`, and update the peer status through
1352/// `heartbeat_ts_collector`.
1353///
1354/// # Correctness
1355///
1356/// To prevent hangs:
1357/// - every await that depends on the network must have a timeout (or interval)
1358/// - every error/shutdown must update the address book state and return
1359///
1360/// The address book state can be updated via `ClientRequest.tx`, or the
1361/// heartbeat_ts_collector.
1362///
1363/// Returning from this function terminates the connection's heartbeat task.
1364async fn send_periodic_heartbeats_with_shutdown_handle(
1365 connected_addr: ConnectedAddr,
1366 shutdown_rx: oneshot::Receiver<CancelHeartbeatTask>,
1367 server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1368 heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1369) -> Result<(), BoxError> {
1370 use futures::future::Either;
1371
1372 let heartbeat_run_loop = send_periodic_heartbeats_run_loop(
1373 connected_addr,
1374 server_tx,
1375 heartbeat_ts_collector.clone(),
1376 );
1377
1378 pin_mut!(shutdown_rx);
1379 pin_mut!(heartbeat_run_loop);
1380
1381 // CORRECTNESS
1382 //
1383 // Currently, select prefers the first future if multiple
1384 // futures are ready.
1385 //
1386 // Starvation is impossible here, because interval has a
1387 // slow rate, and shutdown is a oneshot. If both futures
1388 // are ready, we want the shutdown to take priority over
1389 // sending a useless heartbeat.
1390 match future::select(shutdown_rx, heartbeat_run_loop).await {
1391 Either::Left((Ok(CancelHeartbeatTask), _unused_run_loop)) => {
1392 tracing::trace!("shutting down because Client requested shut down");
1393 handle_heartbeat_shutdown(
1394 PeerError::ClientCancelledHeartbeatTask,
1395 &heartbeat_ts_collector,
1396 &connected_addr,
1397 )
1398 .await
1399 }
1400 Either::Left((Err(oneshot::Canceled), _unused_run_loop)) => {
1401 tracing::trace!("shutting down because Client was dropped");
1402 handle_heartbeat_shutdown(
1403 PeerError::ClientDropped,
1404 &heartbeat_ts_collector,
1405 &connected_addr,
1406 )
1407 .await
1408 }
1409 Either::Right((result, _unused_shutdown)) => {
1410 tracing::trace!("shutting down due to heartbeat failure");
1411 // heartbeat_timeout() already send an error on the timestamp collector channel
1412
1413 result
1414 }
1415 }
1416}
1417
1418/// Send periodical heartbeats to `server_tx`, and update the peer status through
1419/// `heartbeat_ts_collector`.
1420///
1421/// See `send_periodic_heartbeats_with_shutdown_handle` for details.
1422async fn send_periodic_heartbeats_run_loop(
1423 connected_addr: ConnectedAddr,
1424 mut server_tx: futures::channel::mpsc::Sender<ClientRequest>,
1425 heartbeat_ts_collector: tokio::sync::mpsc::Sender<MetaAddrChange>,
1426) -> Result<(), BoxError> {
1427 // Don't send the first heartbeat immediately - we've just completed the handshake!
1428 let mut interval = tokio::time::interval_at(
1429 Instant::now() + constants::HEARTBEAT_INTERVAL,
1430 constants::HEARTBEAT_INTERVAL,
1431 );
1432 // If the heartbeat is delayed, also delay all future heartbeats.
1433 // (Shorter heartbeat intervals just add load, without any benefit.)
1434 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
1435
1436 let mut interval_stream = IntervalStream::new(interval);
1437
1438 while let Some(_instant) = interval_stream.next().await {
1439 // We've reached another heartbeat interval without
1440 // shutting down, so do a heartbeat request.
1441 let ping_sent_at = Instant::now();
1442 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1443 let _ = heartbeat_ts_collector
1444 .send(MetaAddr::new_ping_sent(book_addr, ping_sent_at.into()))
1445 .await;
1446 }
1447
1448 let heartbeat = send_one_heartbeat(&mut server_tx);
1449 let rtt = heartbeat_timeout(heartbeat, &heartbeat_ts_collector, &connected_addr).await?;
1450
1451 // # Security
1452 //
1453 // Peer heartbeats are rate-limited because:
1454 // - opening connections is rate-limited
1455 // - the number of connections is limited
1456 // - Zebra initiates each heartbeat using a timer
1457 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1458 if let Some(rtt) = rtt {
1459 // the collector doesn't depend on network activity,
1460 // so this await should not hang
1461 let _ = heartbeat_ts_collector
1462 .send(MetaAddr::new_responded(book_addr, Some(rtt)))
1463 .await;
1464 }
1465 }
1466 }
1467
1468 unreachable!("unexpected IntervalStream termination")
1469}
1470
1471/// Send one heartbeat using `server_tx`.
1472async fn send_one_heartbeat(
1473 server_tx: &mut futures::channel::mpsc::Sender<ClientRequest>,
1474) -> Result<Response, BoxError> {
1475 // We just reached a heartbeat interval, so start sending
1476 // a heartbeat.
1477 let (tx, rx) = oneshot::channel();
1478
1479 // Try to send the heartbeat request
1480 let request = Request::Ping(Nonce::default());
1481 tracing::trace!(?request, "queueing heartbeat request");
1482 match server_tx.try_send(ClientRequest {
1483 request,
1484 tx,
1485 // we're not requesting inventory, so we don't need to update the registry
1486 inv_collector: None,
1487 transient_addr: None,
1488 span: tracing::Span::current(),
1489 }) {
1490 Ok(()) => {}
1491 Err(e) => {
1492 if e.is_disconnected() {
1493 Err(PeerError::ConnectionClosed)?;
1494 } else if e.is_full() {
1495 // Send the message when the Client becomes ready.
1496 // If sending takes too long, the heartbeat timeout will elapse
1497 // and close the connection, reducing our load to busy peers.
1498 server_tx.send(e.into_inner()).await?;
1499 } else {
1500 // we need to map unexpected error types to PeerErrors
1501 warn!(?e, "unexpected try_send error");
1502 Err(e)?;
1503 };
1504 }
1505 }
1506
1507 // Flush the heartbeat request from the queue
1508 server_tx.flush().await?;
1509 tracing::trace!("sent heartbeat request");
1510
1511 // Heartbeats are checked internally to the
1512 // connection logic, but we need to wait on the
1513 // response to avoid canceling the request.
1514 let response = rx.await??;
1515 tracing::trace!(?response, "got heartbeat response");
1516
1517 Ok(response)
1518}
1519
1520/// Wrap `fut` in a timeout, handing any inner or outer errors using
1521/// `handle_heartbeat_error`.
1522async fn heartbeat_timeout(
1523 fut: impl Future<Output = Result<Response, BoxError>>,
1524 address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1525 connected_addr: &ConnectedAddr,
1526) -> Result<Option<Duration>, BoxError> {
1527 let response = match timeout(constants::HEARTBEAT_INTERVAL, fut).await {
1528 Ok(inner_result) => {
1529 handle_heartbeat_error(inner_result, address_book_updater, connected_addr).await?
1530 }
1531 Err(elapsed) => {
1532 handle_heartbeat_error(Err(elapsed), address_book_updater, connected_addr).await?
1533 }
1534 };
1535
1536 let rtt = match response {
1537 Response::Pong(rtt) => Some(rtt),
1538 _ => None,
1539 };
1540
1541 Ok(rtt)
1542}
1543
1544/// If `result.is_err()`, mark `connected_addr` as failed using `address_book_updater`.
1545async fn handle_heartbeat_error<T, E>(
1546 result: Result<T, E>,
1547 address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1548 connected_addr: &ConnectedAddr,
1549) -> Result<T, E>
1550where
1551 E: std::fmt::Debug,
1552{
1553 match result {
1554 Ok(t) => Ok(t),
1555 Err(err) => {
1556 tracing::debug!(?err, "heartbeat error, shutting down");
1557
1558 // # Security
1559 //
1560 // Peer errors and shutdowns are rate-limited because:
1561 // - opening connections is rate-limited
1562 // - the number of connections is limited
1563 // - after the first error or shutdown, the peer is disconnected
1564 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1565 let _ = address_book_updater
1566 .send(MetaAddr::new_errored(book_addr, None))
1567 .await;
1568 }
1569 Err(err)
1570 }
1571 }
1572}
1573
1574/// Mark `connected_addr` as shut down using `address_book_updater`.
1575async fn handle_heartbeat_shutdown(
1576 peer_error: PeerError,
1577 address_book_updater: &tokio::sync::mpsc::Sender<MetaAddrChange>,
1578 connected_addr: &ConnectedAddr,
1579) -> Result<(), BoxError> {
1580 tracing::debug!(?peer_error, "client shutdown, shutting down heartbeat");
1581
1582 if let Some(book_addr) = connected_addr.get_address_book_addr() {
1583 let _ = address_book_updater
1584 .send(MetaAddr::new_shutdown(book_addr))
1585 .await;
1586 }
1587
1588 Err(peer_error.into())
1589}