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