zebra_network/meta_addr.rs
1//! An address-with-metadata type used in Bitcoin networking.
2
3use std::{
4 cmp::{max, Ordering},
5 time::{Duration, Instant},
6};
7
8use chrono::Utc;
9
10use zebra_chain::{parameters::Network, serialization::DateTime32};
11
12use crate::{
13 constants,
14 peer::{address_is_valid_for_outbound_connections, PeerPreference},
15 protocol::{
16 external::{canonical_peer_addr, types::Version},
17 types::PeerServices,
18 },
19};
20
21use MetaAddrChange::*;
22use PeerAddrState::*;
23
24pub mod peer_addr;
25
26pub use peer_addr::PeerSocketAddr;
27
28#[cfg(any(test, feature = "proptest-impl"))]
29use proptest_derive::Arbitrary;
30
31#[cfg(any(test, feature = "proptest-impl"))]
32use crate::protocol::external::arbitrary::canonical_peer_addr_strategy;
33
34#[cfg(any(test, feature = "proptest-impl"))]
35pub(crate) mod arbitrary;
36
37#[cfg(test)]
38pub(crate) mod tests;
39
40/// Peer connection state, based on our interactions with the peer.
41///
42/// Zebra also tracks how recently a peer has sent us messages, and derives peer
43/// liveness based on the current time. This derived state is tracked using
44/// [`maybe_connected_peers`][mcp] and
45/// [`reconnection_peers`][rp].
46///
47/// [mcp]: crate::AddressBook::maybe_connected_peers
48/// [rp]: crate::AddressBook::reconnection_peers
49#[derive(Copy, Clone, Debug, Eq, PartialEq)]
50#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
51pub enum PeerAddrState {
52 /// The peer has sent us a valid message.
53 ///
54 /// Peers remain in this state, even if they stop responding to requests.
55 /// (Peer liveness is derived from the `last_seen` timestamp, and the current
56 /// time.)
57 Responded,
58
59 /// The peer's address has just been fetched from a DNS seeder, or via peer
60 /// gossip, or as part of a `Version` message, or guessed from an inbound remote IP,
61 /// but we haven't attempted to connect to it yet.
62 NeverAttemptedGossiped,
63
64 /// The peer's TCP connection failed, or the peer sent us an unexpected
65 /// Zcash protocol message, so we failed the connection.
66 Failed,
67
68 /// We just started a connection attempt to this peer.
69 AttemptPending,
70}
71
72impl std::fmt::Display for PeerAddrState {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match self {
75 Responded => write!(f, "connected"),
76 NeverAttemptedGossiped => write!(f, "never_connected"),
77 Failed => write!(f, "failed"),
78 AttemptPending => write!(f, "connecting"),
79 }
80 }
81}
82
83impl PeerAddrState {
84 /// Return true if this state is a "never attempted" state.
85 pub fn is_never_attempted(&self) -> bool {
86 match self {
87 NeverAttemptedGossiped => true,
88 AttemptPending | Responded | Failed => false,
89 }
90 }
91
92 /// Returns the typical connection state machine order of `self` and `other`.
93 /// Partially ordered states are sorted in connection attempt order.
94 ///
95 /// See [`MetaAddrChange::apply_to_meta_addr()`] for more details.
96 fn connection_state_order(&self, other: &Self) -> Ordering {
97 use Ordering::*;
98 match (self, other) {
99 _ if self == other => Equal,
100 // Peers start in the "never attempted" state,
101 // then typically progress towards a "responded" or "failed" state.
102 (NeverAttemptedGossiped, _) => Less,
103 (_, NeverAttemptedGossiped) => Greater,
104 (AttemptPending, _) => Less,
105 (_, AttemptPending) => Greater,
106 (Responded, _) => Less,
107 (_, Responded) => Greater,
108 // These patterns are redundant, but Rust doesn't assume that `==` is reflexive,
109 // so the first is still required (but unreachable).
110 (Failed, _) => Less,
111 //(_, Failed) => Greater,
112 }
113 }
114}
115
116// non-test code should explicitly specify the peer address state
117#[cfg(test)]
118#[allow(clippy::derivable_impls)]
119impl Default for PeerAddrState {
120 fn default() -> Self {
121 NeverAttemptedGossiped
122 }
123}
124
125impl Ord for PeerAddrState {
126 /// `PeerAddrState`s are sorted in approximate reconnection attempt
127 /// order, ignoring liveness.
128 ///
129 /// See [`candidate_set`] and [`MetaAddr::cmp`] for more details.
130 ///
131 /// [`candidate_set`]: super::peer_set::candidate_set
132 fn cmp(&self, other: &Self) -> Ordering {
133 use Ordering::*;
134 match (self, other) {
135 _ if self == other => Equal,
136 // We reconnect to `Responded` peers that have stopped sending messages,
137 // then `NeverAttempted` peers, then `Failed` peers
138 (Responded, _) => Less,
139 (_, Responded) => Greater,
140 (NeverAttemptedGossiped, _) => Less,
141 (_, NeverAttemptedGossiped) => Greater,
142 (Failed, _) => Less,
143 (_, Failed) => Greater,
144 // These patterns are redundant, but Rust doesn't assume that `==` is reflexive,
145 // so the first is still required (but unreachable).
146 (AttemptPending, _) => Less,
147 //(_, AttemptPending) => Greater,
148 }
149 }
150}
151
152impl PartialOrd for PeerAddrState {
153 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
154 Some(self.cmp(other))
155 }
156}
157
158/// An address with metadata on its advertised services and last-seen time.
159///
160/// This struct can be created from `addr` or `addrv2` messages.
161///
162/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
163#[derive(Clone, Debug)]
164#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
165pub struct MetaAddr {
166 /// The peer's canonical socket address.
167 #[cfg_attr(
168 any(test, feature = "proptest-impl"),
169 proptest(strategy = "canonical_peer_addr_strategy()")
170 )]
171 //
172 // TODO: make addr private, so the constructors can make sure it is a
173 // canonical SocketAddr (#2357)
174 pub(crate) addr: PeerSocketAddr,
175
176 /// The services advertised by the peer.
177 ///
178 /// The exact meaning depends on `last_connection_state`:
179 /// - `Responded`: the services advertised by this peer, the last time we
180 /// performed a handshake with it
181 /// - `NeverAttempted`: the unverified services advertised by another peer,
182 /// then gossiped by the peer that sent us this address
183 /// - `Failed` or `AttemptPending`: unverified services via another peer,
184 /// or services advertised in a previous handshake
185 ///
186 /// ## Security
187 ///
188 /// `services` from `NeverAttempted` peers may be invalid due to outdated
189 /// records, older peer versions, or buggy or malicious peers.
190 //
191 // TODO: make services private
192 // split gossiped and handshake services? (#2324)
193 pub(crate) services: Option<PeerServices>,
194
195 /// The unverified "last seen time" gossiped by the remote peer that sent us
196 /// this address.
197 ///
198 /// See the [`MetaAddr::last_seen`] method for details.
199 untrusted_last_seen: Option<DateTime32>,
200
201 /// The last time we received a message from this peer.
202 ///
203 /// See the [`MetaAddr::last_seen`] method for details.
204 last_response: Option<DateTime32>,
205
206 /// The last measured round-trip time (RTT) for this peer, if available.
207 ///
208 /// This value is updated when the peer responds to a ping (Pong).
209 rtt: Option<Duration>,
210
211 /// The last time we sent a ping to this peer.
212 ///
213 /// This value is updated each time a heartbeat ping is sent,
214 /// even if we never receive a response.
215 ping_sent_at: Option<Instant>,
216
217 /// The last time we tried to open an outbound connection to this peer.
218 ///
219 /// See the [`MetaAddr::last_attempt`] method for details.
220 last_attempt: Option<Instant>,
221
222 /// The last time our outbound connection with this peer failed.
223 ///
224 /// See the [`MetaAddr::last_failure`] method for details.
225 last_failure: Option<Instant>,
226
227 /// The outcome of our most recent communication attempt with this peer.
228 //
229 // TODO: move the time and services fields into PeerAddrState?
230 // then some fields could be required in some states
231 pub(crate) last_connection_state: PeerAddrState,
232
233 /// Whether this peer address was added to the address book
234 /// when the peer made an inbound connection.
235 is_inbound: bool,
236
237 /// The user agent string reported by the peer during handshake, if available.
238 user_agent: Option<String>,
239
240 /// The protocol version negotiated with the peer during handshake, if available.
241 negotiated_version: Option<Version>,
242}
243
244/// A change to an existing `MetaAddr`.
245#[derive(Clone, Debug, Eq, PartialEq)]
246#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
247pub enum MetaAddrChange {
248 // TODO:
249 // - split the common `addr` field into an outer struct
250 //
251 /// Creates a `MetaAddr` for an initial peer.
252 NewInitial {
253 #[cfg_attr(
254 any(test, feature = "proptest-impl"),
255 proptest(strategy = "canonical_peer_addr_strategy()")
256 )]
257 addr: PeerSocketAddr,
258 },
259
260 /// Creates a new gossiped `MetaAddr`.
261 NewGossiped {
262 #[cfg_attr(
263 any(test, feature = "proptest-impl"),
264 proptest(strategy = "canonical_peer_addr_strategy()")
265 )]
266 addr: PeerSocketAddr,
267 untrusted_services: PeerServices,
268 untrusted_last_seen: DateTime32,
269 },
270
271 /// Creates new local listener `MetaAddr`.
272 NewLocal {
273 #[cfg_attr(
274 any(test, feature = "proptest-impl"),
275 proptest(strategy = "canonical_peer_addr_strategy()")
276 )]
277 addr: PeerSocketAddr,
278 },
279
280 /// Updates an existing `MetaAddr` when an outbound connection attempt
281 /// starts.
282 UpdateAttempt {
283 #[cfg_attr(
284 any(test, feature = "proptest-impl"),
285 proptest(strategy = "canonical_peer_addr_strategy()")
286 )]
287 addr: PeerSocketAddr,
288 },
289
290 /// Updates an existing `MetaAddr` when we've made a successful connection with a peer.
291 UpdateConnected {
292 #[cfg_attr(
293 any(test, feature = "proptest-impl"),
294 proptest(strategy = "canonical_peer_addr_strategy()")
295 )]
296 addr: PeerSocketAddr,
297 services: PeerServices,
298 is_inbound: bool,
299 user_agent: String,
300 negotiated_version: Version,
301 },
302
303 /// Updates an existing `MetaAddr` when we send a ping to a peer.
304 UpdatePingSent {
305 #[cfg_attr(
306 any(test, feature = "proptest-impl"),
307 proptest(strategy = "canonical_peer_addr_strategy()")
308 )]
309 addr: PeerSocketAddr,
310 ping_sent_at: Instant,
311 },
312
313 /// Updates an existing `MetaAddr` when a peer responds with a message.
314 UpdateResponded {
315 #[cfg_attr(
316 any(test, feature = "proptest-impl"),
317 proptest(strategy = "canonical_peer_addr_strategy()")
318 )]
319 addr: PeerSocketAddr,
320 rtt: Option<Duration>,
321 },
322
323 /// Updates an existing `MetaAddr` when a peer fails.
324 UpdateFailed {
325 #[cfg_attr(
326 any(test, feature = "proptest-impl"),
327 proptest(strategy = "canonical_peer_addr_strategy()")
328 )]
329 addr: PeerSocketAddr,
330 services: Option<PeerServices>,
331 },
332
333 /// Bans a peer's group when it misbehaves, such as by advertising
334 /// semantically invalid blocks or transactions.
335 #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
336 UpdateMisbehavior {
337 addr: PeerSocketAddr,
338 score_increment: u32,
339 },
340}
341
342impl MetaAddr {
343 /// Returns a [`MetaAddrChange::NewInitial`] for a peer that was excluded from
344 /// the list of the initial peers.
345 pub fn new_initial_peer(addr: PeerSocketAddr) -> MetaAddrChange {
346 NewInitial {
347 addr: canonical_peer_addr(addr),
348 }
349 }
350
351 /// Returns a new `MetaAddr`, based on the deserialized fields from a
352 /// gossiped peer [`Addr`][crate::protocol::external::Message::Addr] message.
353 pub fn new_gossiped_meta_addr(
354 addr: PeerSocketAddr,
355 untrusted_services: PeerServices,
356 untrusted_last_seen: DateTime32,
357 ) -> MetaAddr {
358 MetaAddr {
359 addr: canonical_peer_addr(addr),
360 services: Some(untrusted_services),
361 untrusted_last_seen: Some(untrusted_last_seen),
362 last_response: None,
363 rtt: None,
364 ping_sent_at: None,
365 last_attempt: None,
366 last_failure: None,
367 last_connection_state: NeverAttemptedGossiped,
368 is_inbound: false,
369 user_agent: None,
370 negotiated_version: None,
371 }
372 }
373
374 /// Returns a [`MetaAddrChange::NewGossiped`], based on a gossiped peer
375 /// [`MetaAddr`].
376 ///
377 /// Returns [`None`] if the gossiped peer is missing the untrusted services field.
378 #[allow(clippy::unwrap_in_result)]
379 pub fn new_gossiped_change(self) -> Option<MetaAddrChange> {
380 let untrusted_services = self.services?;
381
382 Some(NewGossiped {
383 addr: canonical_peer_addr(self.addr),
384 untrusted_services,
385 untrusted_last_seen: self
386 .untrusted_last_seen
387 .expect("unexpected missing last seen"),
388 })
389 }
390
391 /// Returns a [`MetaAddrChange::UpdateConnected`] for a peer that has just successfully
392 /// connected.
393 ///
394 /// # Security
395 ///
396 /// This address must be the remote address from an outbound connection,
397 /// and the services must be the services from that peer's handshake.
398 ///
399 /// Otherwise:
400 /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
401 /// state, or
402 /// - Zebra could advertise unreachable addresses to its own peers.
403 pub fn new_connected(
404 addr: PeerSocketAddr,
405 services: &PeerServices,
406 is_inbound: bool,
407 user_agent: String,
408 negotiated_version: Version,
409 ) -> MetaAddrChange {
410 UpdateConnected {
411 addr: canonical_peer_addr(*addr),
412 services: *services,
413 is_inbound,
414 user_agent,
415 negotiated_version,
416 }
417 }
418
419 /// Returns a [`MetaAddrChange::UpdatePingSent`] for a peer that we just sent a ping to.
420 pub fn new_ping_sent(addr: PeerSocketAddr, ping_sent_at: Instant) -> MetaAddrChange {
421 UpdatePingSent {
422 addr: canonical_peer_addr(*addr),
423 ping_sent_at,
424 }
425 }
426
427 /// Returns a [`MetaAddrChange::UpdateResponded`] for a peer that has just
428 /// sent us a message.
429 ///
430 /// # Security
431 ///
432 /// This address must be the remote address from an outbound connection.
433 ///
434 /// Otherwise:
435 /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
436 /// state, or
437 /// - Zebra could advertise unreachable addresses to its own peers.
438 pub fn new_responded(addr: PeerSocketAddr, rtt: Option<Duration>) -> MetaAddrChange {
439 UpdateResponded {
440 addr: canonical_peer_addr(*addr),
441 rtt,
442 }
443 }
444
445 /// Returns a [`MetaAddrChange::UpdateAttempt`] for a peer that we
446 /// want to make an outbound connection to.
447 pub fn new_reconnect(addr: PeerSocketAddr) -> MetaAddrChange {
448 UpdateAttempt {
449 addr: canonical_peer_addr(*addr),
450 }
451 }
452
453 /// Returns a [`MetaAddrChange::NewLocal`] for our own listener address.
454 pub fn new_local_listener_change(addr: impl Into<PeerSocketAddr>) -> MetaAddrChange {
455 NewLocal {
456 addr: canonical_peer_addr(addr),
457 }
458 }
459
460 /// Returns a [`MetaAddrChange::UpdateFailed`] for a peer that has just had an error.
461 pub fn new_errored(
462 addr: PeerSocketAddr,
463 services: impl Into<Option<PeerServices>>,
464 ) -> MetaAddrChange {
465 UpdateFailed {
466 addr: canonical_peer_addr(*addr),
467 services: services.into(),
468 }
469 }
470
471 /// Returns a [`MetaAddrChange::UpdateMisbehavior`] for a peer that has misbehaved.
472 ///
473 /// Canonicalizes the address to match the form stored by a successful handshake
474 /// (`new_connected`). On Linux dual-stack sockets, inbound IPv4 connections
475 /// arrive as IPv4-mapped IPv6 addresses (`::ffff:A.B.C.D`); without
476 /// canonicalization, `apply_to_meta_addr` panics on the addr invariant.
477 pub fn new_misbehavior(addr: PeerSocketAddr, score_increment: u32) -> MetaAddrChange {
478 UpdateMisbehavior {
479 addr: canonical_peer_addr(*addr),
480 score_increment,
481 }
482 }
483
484 /// Create a new `MetaAddr` for a peer that has just shut down.
485 pub fn new_shutdown(addr: PeerSocketAddr) -> MetaAddrChange {
486 // TODO: if the peer shut down in the Responded state, preserve that
487 // state. All other states should be treated as (timeout) errors.
488 MetaAddr::new_errored(addr, None)
489 }
490
491 /// Return the address for this `MetaAddr`.
492 pub fn addr(&self) -> PeerSocketAddr {
493 self.addr
494 }
495
496 /// Return the address preference level for this `MetaAddr`.
497 pub fn peer_preference(&self) -> Result<PeerPreference, &'static str> {
498 PeerPreference::new(self.addr, None)
499 }
500
501 /// Returns the time of the last successful interaction with this peer.
502 ///
503 /// Initially set to the unverified "last seen time" gossiped by the remote
504 /// peer that sent us this address.
505 ///
506 /// If the `last_connection_state` has ever been `Responded`, this field is
507 /// set to the last time we processed a message from this peer.
508 ///
509 /// ## Security
510 ///
511 /// `last_seen` times from peers that have never `Responded` may be
512 /// incorrect due to clock skew, or buggy or malicious peers.
513 pub fn last_seen(&self) -> Option<DateTime32> {
514 self.last_response.or(self.untrusted_last_seen)
515 }
516
517 /// Returns whether the address is from an inbound peer connection
518 pub fn is_inbound(&self) -> bool {
519 self.is_inbound
520 }
521
522 /// Returns the round-trip time (RTT) for this peer, if available.
523 pub fn rtt(&self) -> Option<Duration> {
524 self.rtt
525 }
526
527 /// Returns the time this peer was last pinged, if available.
528 pub fn ping_sent_at(&self) -> Option<Instant> {
529 self.ping_sent_at
530 }
531
532 /// Returns the unverified "last seen time" gossiped by the remote peer that
533 /// sent us this address.
534 ///
535 /// See the [`MetaAddr::last_seen`] method for details.
536 //
537 // TODO: pub(in crate::address_book) - move meta_addr into address_book
538 pub(crate) fn untrusted_last_seen(&self) -> Option<DateTime32> {
539 self.untrusted_last_seen
540 }
541
542 /// Returns the last time we received a message from this peer.
543 ///
544 /// See the [`MetaAddr::last_seen`] method for details.
545 //
546 // TODO: pub(in crate::address_book) - move meta_addr into address_book
547 #[allow(dead_code)]
548 pub(crate) fn last_response(&self) -> Option<DateTime32> {
549 self.last_response
550 }
551
552 /// Set the gossiped untrusted last seen time for this peer.
553 pub(crate) fn set_untrusted_last_seen(&mut self, untrusted_last_seen: DateTime32) {
554 self.untrusted_last_seen = Some(untrusted_last_seen);
555 }
556
557 /// Returns the time of our last outbound connection attempt with this peer.
558 ///
559 /// If the `last_connection_state` has ever been `AttemptPending`, this
560 /// field is set to the last time we started an outbound connection attempt
561 /// with this peer.
562 pub fn last_attempt(&self) -> Option<Instant> {
563 self.last_attempt
564 }
565
566 /// Returns the time of our last failed outbound connection with this peer.
567 ///
568 /// If the `last_connection_state` has ever been `Failed`, this field is set
569 /// to the last time:
570 /// - a connection attempt failed, or
571 /// - an open connection encountered a fatal protocol error.
572 pub fn last_failure(&self) -> Option<Instant> {
573 self.last_failure
574 }
575
576 /// Have we had any recently messages from this peer?
577 ///
578 /// Returns `true` if the peer is likely connected and responsive in the peer
579 /// set.
580 ///
581 /// [`constants::MIN_PEER_RECONNECTION_DELAY`] represents the time interval in which
582 /// we should receive at least one message from a peer, or close the
583 /// connection. Therefore, if the last-seen timestamp is older than
584 /// [`constants::MIN_PEER_RECONNECTION_DELAY`] ago, we know we should have
585 /// disconnected from it. Otherwise, we could potentially be connected to it.
586 pub fn has_connection_recently_responded(&self, now: chrono::DateTime<Utc>) -> bool {
587 if let Some(last_response) = self.last_response {
588 // Recent times and future times are considered live
589 last_response.saturating_elapsed(now)
590 <= constants::MIN_PEER_RECONNECTION_DELAY
591 .try_into()
592 .expect("unexpectedly large constant")
593 } else {
594 // If there has never been any response, it can't possibly be live
595 false
596 }
597 }
598
599 /// Have we recently attempted an outbound connection to this peer?
600 ///
601 /// Returns `true` if this peer was recently attempted, or has a connection
602 /// attempt in progress.
603 pub fn was_connection_recently_attempted(&self, now: Instant) -> bool {
604 if let Some(last_attempt) = self.last_attempt {
605 // Recent times and future times are considered live.
606 // Instants are monotonic, so `now` should always be later than `last_attempt`,
607 // except for synthetic data in tests.
608 now.saturating_duration_since(last_attempt) <= constants::MIN_PEER_RECONNECTION_DELAY
609 } else {
610 // If there has never been any attempt, it can't possibly be live
611 false
612 }
613 }
614
615 /// Have we recently had a failed connection to this peer?
616 ///
617 /// Returns `true` if this peer has recently failed.
618 pub fn has_connection_recently_failed(&self, now: Instant) -> bool {
619 if let Some(last_failure) = self.last_failure {
620 // Recent times and future times are considered live
621 now.saturating_duration_since(last_failure) <= constants::MIN_PEER_RECONNECTION_DELAY
622 } else {
623 // If there has never been any failure, it can't possibly be recent
624 false
625 }
626 }
627
628 /// Returns true if this peer has recently sent us a message.
629 pub fn was_recently_live(&self, now: chrono::DateTime<Utc>) -> bool {
630 // NeverAttempted, Failed, and AttemptPending peers should never be live
631 self.last_connection_state == PeerAddrState::Responded
632 && self.has_connection_recently_responded(now)
633 }
634
635 /// Has this peer been seen recently?
636 ///
637 /// Returns `true` if this peer has responded recently or if the peer was gossiped with a
638 /// recent reported last seen time.
639 ///
640 /// [`constants::MAX_PEER_ACTIVE_FOR_GOSSIP`] represents the maximum time since a peer was seen
641 /// to still be considered reachable.
642 pub fn is_active_for_gossip(&self, now: chrono::DateTime<Utc>) -> bool {
643 if let Some(last_seen) = self.last_seen() {
644 // Correctness: `last_seen` shouldn't ever be in the future, either because we set the
645 // time or because another peer's future time was sanitized when it was added to the
646 // address book
647 last_seen.saturating_elapsed(now) <= constants::MAX_PEER_ACTIVE_FOR_GOSSIP
648 } else {
649 // Peer has never responded and does not have a gossiped last seen time
650 false
651 }
652 }
653
654 /// Returns true if any messages were recently sent to or received from this address.
655 pub fn was_recently_updated(
656 &self,
657 instant_now: Instant,
658 chrono_now: chrono::DateTime<Utc>,
659 ) -> bool {
660 self.has_connection_recently_responded(chrono_now)
661 || self.was_connection_recently_attempted(instant_now)
662 || self.has_connection_recently_failed(instant_now)
663 }
664
665 /// Is this address ready for a new outbound connection attempt?
666 pub fn is_ready_for_connection_attempt(
667 &self,
668 instant_now: Instant,
669 chrono_now: chrono::DateTime<Utc>,
670 network: &Network,
671 ) -> bool {
672 self.last_known_info_is_valid_for_outbound(network)
673 && !self.was_recently_updated(instant_now, chrono_now)
674 && self.is_probably_reachable(chrono_now)
675 }
676
677 /// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
678 /// connections?
679 ///
680 /// Since the addresses in the address book are unique, this check can be
681 /// used to permanently reject entire [`MetaAddr`]s.
682 pub fn address_is_valid_for_outbound(&self, network: &Network) -> bool {
683 address_is_valid_for_outbound_connections(self.addr, network.clone()).is_ok()
684 }
685
686 /// Is the last known information for this peer valid for outbound
687 /// connections?
688 ///
689 /// The last known info might be outdated or untrusted, so this check can
690 /// only be used to:
691 /// - reject `NeverAttempted...` [`MetaAddrChange`]s, and
692 /// - temporarily stop outbound connections to a [`MetaAddr`].
693 pub fn last_known_info_is_valid_for_outbound(&self, network: &Network) -> bool {
694 let is_node = match self.services {
695 Some(services) => services.contains(PeerServices::NODE_NETWORK),
696 None => true,
697 };
698
699 is_node && self.address_is_valid_for_outbound(network)
700 }
701
702 /// Should this peer considered reachable?
703 ///
704 /// A peer is probably reachable if:
705 /// - it has never been attempted, or
706 /// - the last connection attempt was successful, or
707 /// - the last successful connection was less than 3 days ago.
708 ///
709 /// # Security
710 ///
711 /// This is used by [`Self::is_ready_for_connection_attempt`] so that Zebra stops trying to
712 /// connect to peers that are likely unreachable.
713 ///
714 /// The `untrusted_last_seen` time is used as a fallback time if the local node has never
715 /// itself seen the peer. If the reported last seen time is a long time ago or `None`, then the local
716 /// node will attempt to connect the peer once, and if that attempt fails it won't
717 /// try to connect ever again. (The state can't be `Failed` until after the first connection attempt.)
718 pub fn is_probably_reachable(&self, now: chrono::DateTime<Utc>) -> bool {
719 self.last_connection_state != PeerAddrState::Failed || self.last_seen_is_recent(now)
720 }
721
722 /// Was this peer last seen recently?
723 ///
724 /// Returns `true` if this peer was last seen at most
725 /// [`MAX_RECENT_PEER_AGE`][constants::MAX_RECENT_PEER_AGE] ago.
726 /// Returns false if the peer is outdated, or it has no last seen time.
727 pub fn last_seen_is_recent(&self, now: chrono::DateTime<Utc>) -> bool {
728 match self.last_seen() {
729 Some(last_seen) => last_seen.saturating_elapsed(now) <= constants::MAX_RECENT_PEER_AGE,
730 None => false,
731 }
732 }
733
734 /// Returns the services advertised by the peer, if available.
735 pub fn services(&self) -> Option<PeerServices> {
736 self.services
737 }
738
739 /// Returns the last known connection state for this peer.
740 pub fn last_connection_state(&self) -> PeerAddrState {
741 self.last_connection_state
742 }
743
744 /// Returns the user agent string reported by this peer, if available.
745 pub fn user_agent(&self) -> Option<&str> {
746 self.user_agent.as_deref()
747 }
748
749 /// Returns the negotiated protocol version for this peer, if available.
750 pub fn negotiated_version(&self) -> Option<Version> {
751 self.negotiated_version
752 }
753
754 /// Return a sanitized version of this `MetaAddr`, for sending to a remote peer.
755 ///
756 /// Returns `None` if this `MetaAddr` should not be sent to remote peers.
757 #[allow(clippy::unwrap_in_result)]
758 pub fn sanitize(&self, network: &Network) -> Option<MetaAddr> {
759 if !self.last_known_info_is_valid_for_outbound(network) {
760 return None;
761 }
762
763 // Inbound addresses are ephemeral remote ports, not listener ports.
764 if self.is_inbound {
765 return None;
766 }
767
768 // Sanitize time
769 let last_seen = self.last_seen()?;
770 let remainder = last_seen
771 .timestamp()
772 .rem_euclid(crate::constants::TIMESTAMP_TRUNCATION_SECONDS);
773 let last_seen = last_seen
774 .checked_sub(remainder.into())
775 .expect("unexpected underflow: rem_euclid is strictly less than timestamp");
776
777 Some(MetaAddr {
778 addr: canonical_peer_addr(self.addr),
779 // initial peers are sanitized assuming they are `NODE_NETWORK`
780 // TODO: split untrusted and direct services
781 // consider sanitizing untrusted services to NODE_NETWORK (#2324)
782 services: self.services.or(Some(PeerServices::NODE_NETWORK)),
783 // only put the last seen time in the untrusted field,
784 // this matches deserialization, and avoids leaking internal state
785 untrusted_last_seen: Some(last_seen),
786 last_response: None,
787 // these fields aren't sent to the remote peer, but sanitize them anyway
788 rtt: None,
789 ping_sent_at: None,
790 last_attempt: None,
791 last_failure: None,
792 last_connection_state: NeverAttemptedGossiped,
793 is_inbound: false,
794 user_agent: None,
795 negotiated_version: None,
796 })
797 }
798}
799
800#[cfg(test)]
801impl MetaAddr {
802 /// Forcefully change the time this peer last responded.
803 ///
804 /// This method is for testing purposes only.
805 pub(crate) fn set_last_response(&mut self, last_response: DateTime32) {
806 self.last_response = Some(last_response);
807 }
808}
809
810impl MetaAddrChange {
811 /// Return the address for this change.
812 pub fn addr(&self) -> PeerSocketAddr {
813 match self {
814 NewInitial { addr }
815 | NewGossiped { addr, .. }
816 | NewLocal { addr, .. }
817 | UpdateAttempt { addr }
818 | UpdateConnected { addr, .. }
819 | UpdatePingSent { addr, .. }
820 | UpdateResponded { addr, .. }
821 | UpdateFailed { addr, .. }
822 | UpdateMisbehavior { addr, .. } => *addr,
823 }
824 }
825
826 #[cfg(any(test, feature = "proptest-impl"))]
827 /// Set the address for this change to `new_addr`.
828 ///
829 /// This method should only be used in tests.
830 pub fn set_addr(&mut self, new_addr: PeerSocketAddr) {
831 match self {
832 NewInitial { addr }
833 | NewGossiped { addr, .. }
834 | NewLocal { addr, .. }
835 | UpdateAttempt { addr }
836 | UpdateConnected { addr, .. }
837 | UpdatePingSent { addr, .. }
838 | UpdateResponded { addr, .. }
839 | UpdateFailed { addr, .. }
840 | UpdateMisbehavior { addr, .. } => *addr = new_addr,
841 }
842 }
843
844 /// Return the untrusted services for this change, if available.
845 pub fn untrusted_services(&self) -> Option<PeerServices> {
846 match self {
847 NewInitial { .. } => None,
848 // TODO: split untrusted and direct services (#2324)
849 NewGossiped {
850 untrusted_services, ..
851 } => Some(*untrusted_services),
852 // TODO: create a "services implemented by Zebra" constant (#2324)
853 NewLocal { .. } => Some(PeerServices::NODE_NETWORK),
854 UpdateAttempt { .. } => None,
855 UpdateConnected { services, .. } => Some(*services),
856 UpdatePingSent { .. } => None,
857 UpdateResponded { .. } => None,
858 UpdateFailed { services, .. } => *services,
859 UpdateMisbehavior { .. } => None,
860 }
861 }
862
863 /// Return the untrusted last seen time for this change, if available.
864 pub fn untrusted_last_seen(&self, now: DateTime32) -> Option<DateTime32> {
865 match self {
866 NewInitial { .. } => None,
867 NewGossiped {
868 untrusted_last_seen,
869 ..
870 } => Some(*untrusted_last_seen),
871 // We know that our local listener is available
872 NewLocal { .. } => Some(now),
873 UpdateAttempt { .. }
874 | UpdateConnected { .. }
875 | UpdatePingSent { .. }
876 | UpdateResponded { .. }
877 | UpdateFailed { .. }
878 | UpdateMisbehavior { .. } => None,
879 }
880 }
881
882 // # Concurrency
883 //
884 // We assign a time to each change when it is applied to the address book by either the
885 // address book updater or candidate set tasks. This is the time that the change was received
886 // from the updater channel, rather than the time that the message was read from the peer
887 // connection.
888 //
889 // Since the connection tasks run concurrently in an unspecified order, and the address book
890 // updater runs in a separate thread, these times are almost always very similar. If Zebra's
891 // address book is under load, we should use lower rate-limits for new inbound or outbound
892 // connections, disconnections, peer gossip crawls, or peer `UpdateResponded` updates.
893 //
894 // TODO:
895 // - move the time API calls from `impl MetaAddrChange` `last_*()` methods:
896 // - if they impact performance, call them once in the address book updater task,
897 // then apply them to all the waiting changes
898 // - otherwise, move them to the `impl MetaAddrChange` `new_*()` methods,
899 // so they are called in the connection tasks
900 //
901 /// Return the last attempt for this change, if available.
902 pub fn last_attempt(&self, now: Instant) -> Option<Instant> {
903 match self {
904 NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } => None,
905 // Attempt changes are applied before we start the handshake to the
906 // peer address. So the attempt time is a lower bound for the actual
907 // handshake time.
908 UpdateAttempt { .. } => Some(now),
909 UpdateConnected { .. }
910 | UpdatePingSent { .. }
911 | UpdateResponded { .. }
912 | UpdateFailed { .. }
913 | UpdateMisbehavior { .. } => None,
914 }
915 }
916
917 /// Return the last response for this change, if available.
918 pub fn last_response(&self, now: DateTime32) -> Option<DateTime32> {
919 match self {
920 NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } | UpdateAttempt { .. } => None,
921 // If there is a large delay applying this change, then:
922 // - the peer might stay in the `AttemptPending` state for longer,
923 // - we might send outdated last seen times to our peers, and
924 // - the peer will appear to be live for longer, delaying future
925 // reconnection attempts.
926 UpdateConnected { .. } | UpdateResponded { .. } => Some(now),
927 UpdateFailed { .. } | UpdateMisbehavior { .. } => None,
928 UpdatePingSent { .. } => None,
929 }
930 }
931
932 /// Return the timestamp when a ping was last sent, if available.
933 pub fn ping_sent(&self) -> Option<Instant> {
934 match self {
935 UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
936 _ => None,
937 }
938 }
939
940 /// Return the RTT for this change, if available
941 pub fn rtt(&self) -> Option<Duration> {
942 match self {
943 UpdateResponded { rtt, .. } => *rtt,
944 _ => None,
945 }
946 }
947
948 /// Returns the timestamp when a ping was last sent, if available.
949 pub fn ping_sent_at(&self) -> Option<Instant> {
950 match self {
951 UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
952 _ => None,
953 }
954 }
955
956 /// Return the last failure for this change, if available.
957 pub fn last_failure(&self, now: Instant) -> Option<Instant> {
958 match self {
959 NewInitial { .. }
960 | NewGossiped { .. }
961 | NewLocal { .. }
962 | UpdateAttempt { .. }
963 | UpdateConnected { .. }
964 | UpdatePingSent { .. }
965 | UpdateResponded { .. } => None,
966 // If there is a large delay applying this change, then:
967 // - the peer might stay in the `AttemptPending` or `Responded`
968 // states for longer, and
969 // - the peer will appear to be used for longer, delaying future
970 // reconnection attempts.
971 UpdateFailed { .. } | UpdateMisbehavior { .. } => Some(now),
972 }
973 }
974
975 /// Return the peer connection state for this change.
976 pub fn peer_addr_state(&self) -> PeerAddrState {
977 match self {
978 NewInitial { .. } => NeverAttemptedGossiped,
979 NewGossiped { .. } => NeverAttemptedGossiped,
980 // local listeners get sanitized, so the state doesn't matter here
981 NewLocal { .. } => NeverAttemptedGossiped,
982 UpdateAttempt { .. } => AttemptPending,
983 UpdateConnected { .. }
984 // Sending a ping is an interaction with a connected peer, but does not indicate new liveness.
985 // Peers stay in Responded once connected, so we keep them in that state for UpdatePingSent.
986 | UpdatePingSent { .. }
987 | UpdateResponded { .. }
988 | UpdateMisbehavior { .. } => Responded,
989 UpdateFailed { .. } => Failed,
990 }
991 }
992
993 /// Returns the corresponding `MetaAddr` for this change.
994 pub fn into_new_meta_addr(self, instant_now: Instant, local_now: DateTime32) -> MetaAddr {
995 let user_agent = self.user_agent();
996 let negotiated_version = self.negotiated_version();
997 MetaAddr {
998 addr: self.addr(),
999 services: self.untrusted_services(),
1000 untrusted_last_seen: self.untrusted_last_seen(local_now),
1001 last_response: self.last_response(local_now),
1002 rtt: self.rtt(),
1003 ping_sent_at: self.ping_sent_at(),
1004 last_attempt: self.last_attempt(instant_now),
1005 last_failure: self.last_failure(instant_now),
1006 last_connection_state: self.peer_addr_state(),
1007 is_inbound: self.is_inbound(),
1008 user_agent,
1009 negotiated_version,
1010 }
1011 }
1012
1013 /// Returns the misbehavior score for the current change, or `0` if it is not
1014 /// an [`MetaAddrChange::UpdateMisbehavior`] change.
1015 pub fn misbehavior_score(&self) -> u32 {
1016 match self {
1017 MetaAddrChange::UpdateMisbehavior {
1018 score_increment, ..
1019 } => *score_increment,
1020 _ => 0,
1021 }
1022 }
1023
1024 /// Returns whether this change was created for a new inbound connection.
1025 pub fn is_inbound(&self) -> bool {
1026 if let MetaAddrChange::UpdateConnected { is_inbound, .. } = self {
1027 *is_inbound
1028 } else {
1029 false
1030 }
1031 }
1032
1033 /// Returns the user agent from this change, if available.
1034 pub fn user_agent(&self) -> Option<String> {
1035 if let MetaAddrChange::UpdateConnected { user_agent, .. } = self {
1036 Some(user_agent.clone())
1037 } else {
1038 None
1039 }
1040 }
1041
1042 /// Returns the negotiated protocol version from this change, if available.
1043 pub fn negotiated_version(&self) -> Option<Version> {
1044 if let MetaAddrChange::UpdateConnected {
1045 negotiated_version, ..
1046 } = self
1047 {
1048 Some(*negotiated_version)
1049 } else {
1050 None
1051 }
1052 }
1053
1054 /// Returns the corresponding [`MetaAddr`] for a local listener change.
1055 ///
1056 /// This method exists so we don't have to provide an unused [`Instant`] to get a local
1057 /// listener `MetaAddr`.
1058 ///
1059 /// # Panics
1060 ///
1061 /// If this change is not a [`MetaAddrChange::NewLocal`].
1062 pub fn local_listener_into_new_meta_addr(self, local_now: DateTime32) -> MetaAddr {
1063 assert!(matches!(self, MetaAddrChange::NewLocal { .. }));
1064
1065 MetaAddr {
1066 addr: self.addr(),
1067 services: self.untrusted_services(),
1068 untrusted_last_seen: self.untrusted_last_seen(local_now),
1069 last_response: self.last_response(local_now),
1070 rtt: None,
1071 ping_sent_at: None,
1072 last_attempt: None,
1073 last_failure: None,
1074 last_connection_state: self.peer_addr_state(),
1075 is_inbound: self.is_inbound(),
1076 user_agent: None,
1077 negotiated_version: None,
1078 }
1079 }
1080
1081 /// Apply this change to a previous `MetaAddr` from the address book,
1082 /// producing a new or updated `MetaAddr`.
1083 ///
1084 /// If the change isn't valid for the `previous` address, returns `None`.
1085 #[allow(clippy::unwrap_in_result)]
1086 pub fn apply_to_meta_addr(
1087 &self,
1088 previous: impl Into<Option<MetaAddr>>,
1089 instant_now: Instant,
1090 chrono_now: chrono::DateTime<Utc>,
1091 ) -> Option<MetaAddr> {
1092 let local_now: DateTime32 = chrono_now.try_into().expect("will succeed until 2038");
1093
1094 let Some(previous) = previous.into() else {
1095 // no previous: create a new entry
1096 return Some(self.clone().into_new_meta_addr(instant_now, local_now));
1097 };
1098
1099 assert_eq!(previous.addr, self.addr(), "unexpected addr mismatch");
1100
1101 let instant_previous = max(previous.last_attempt, previous.last_failure);
1102 let local_previous = previous.last_response;
1103
1104 // Is this change potentially concurrent with the previous change?
1105 //
1106 // Since we're using saturating arithmetic, one of each pair of less than comparisons
1107 // will always be true, because subtraction saturates to zero.
1108 let change_is_concurrent = instant_previous
1109 .map(|instant_previous| {
1110 instant_previous.saturating_duration_since(instant_now)
1111 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1112 && instant_now.saturating_duration_since(instant_previous)
1113 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1114 })
1115 .unwrap_or_default()
1116 || local_previous
1117 .map(|local_previous| {
1118 local_previous.saturating_duration_since(local_now).to_std()
1119 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1120 && local_now.saturating_duration_since(local_previous).to_std()
1121 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1122 })
1123 .unwrap_or_default();
1124 let change_is_out_of_order = instant_previous
1125 .map(|instant_previous| instant_previous > instant_now)
1126 .unwrap_or_default()
1127 || local_previous
1128 .map(|local_previous| local_previous > local_now)
1129 .unwrap_or_default();
1130
1131 // Is this change typically from a connection state that has more progress?
1132 let connection_has_more_progress = self
1133 .peer_addr_state()
1134 .connection_state_order(&previous.last_connection_state)
1135 == Ordering::Greater;
1136
1137 let previous_has_been_attempted = !previous.last_connection_state.is_never_attempted();
1138 let change_to_never_attempted = self.peer_addr_state().is_never_attempted();
1139 let is_misbehavior_update = self.misbehavior_score() != 0;
1140
1141 // Invalid changes
1142
1143 if change_to_never_attempted && previous_has_been_attempted && !is_misbehavior_update {
1144 // Existing entry has been attempted, change is NeverAttempted
1145 // - ignore the change
1146 //
1147 // # Security
1148 //
1149 // Ignore NeverAttempted changes once we have made an attempt,
1150 // so malicious peers can't keep changing our peer connection order.
1151 return None;
1152 }
1153
1154 if change_is_out_of_order && !change_is_concurrent && !is_misbehavior_update {
1155 // Change is significantly out of order: ignore it.
1156 //
1157 // # Security
1158 //
1159 // Ignore changes that arrive out of order, if they are far enough apart.
1160 // This enforces the peer connection retry interval.
1161 return None;
1162 }
1163
1164 if change_is_concurrent && !connection_has_more_progress && !is_misbehavior_update {
1165 // Change is close together in time, and it would revert the connection to an earlier
1166 // state.
1167 //
1168 // # Security
1169 //
1170 // If the changes might have been concurrent, ignore connection states with less
1171 // progress.
1172 //
1173 // ## Sources of Concurrency
1174 //
1175 // If two changes happen close together, the async scheduler can run their change
1176 // send and apply code in any order. This includes the code that records the time of
1177 // the change. So even if a failure happens after a response message, the failure time
1178 // can be recorded before the response time code is run.
1179 //
1180 // Some machines and OSes have limited time resolution, so we can't guarantee that
1181 // two messages on the same connection will always have different times. There are
1182 // also known bugs impacting monotonic times which make them go backwards or stay
1183 // equal. For wall clock times, clock skew is an expected event, particularly with
1184 // network time server updates.
1185 //
1186 // Also, the application can fail a connection independently and simultaneously
1187 // (or slightly before) a positive update from that peer connection. We want the
1188 // application change to take priority in the address book, because the connection
1189 // state machine also prioritises failures over any other peer messages.
1190 //
1191 // ## Resolution
1192 //
1193 // In these cases, we want to apply the failure, then ignore any nearby changes that
1194 // reset the address book entry to a more appealing state. This prevents peers from
1195 // sending updates right before failing a connection, in order to make themselves more
1196 // likely to get a reconnection.
1197 //
1198 // The connection state machine order is used so that state transitions which are
1199 // typically close together are preserved. These transitions are:
1200 // - NeverAttempted*->AttemptPending->(Responded|Failed)
1201 // - Responded->Failed
1202 //
1203 // State transitions like (Responded|Failed)->AttemptPending only happen after the
1204 // reconnection timeout, so they will never be considered concurrent.
1205 return None;
1206 }
1207
1208 // Valid changes
1209
1210 if change_to_never_attempted && !previous_has_been_attempted {
1211 // Existing entry and change are both NeverAttempted
1212 // - preserve original values of all fields
1213 // - but replace None with Some
1214 //
1215 // # Security
1216 //
1217 // Preserve the original field values for NeverAttempted peers,
1218 // so malicious peers can't keep changing our peer connection order.
1219 Some(MetaAddr {
1220 addr: self.addr(),
1221 services: previous.services.or_else(|| self.untrusted_services()),
1222 untrusted_last_seen: previous
1223 .untrusted_last_seen
1224 .or_else(|| self.untrusted_last_seen(local_now)),
1225 // The peer has not been attempted, so these fields must be None
1226 last_response: None,
1227 rtt: None,
1228 ping_sent_at: None,
1229 last_attempt: None,
1230 last_failure: None,
1231 last_connection_state: self.peer_addr_state(),
1232 is_inbound: previous.is_inbound || self.is_inbound(),
1233 user_agent: None,
1234 negotiated_version: None,
1235 })
1236 } else {
1237 // Existing entry and change are both Attempt, Responded, Failed,
1238 // and the change is later, either in time or in connection progress
1239 // (this is checked above and returns None early):
1240 // - update the fields from the change
1241 Some(MetaAddr {
1242 addr: self.addr(),
1243 // Always update optional fields, unless the update is None.
1244 //
1245 // We want up-to-date services, even if they have fewer bits
1246 services: self.untrusted_services().or(previous.services),
1247 // Only NeverAttempted changes can modify the last seen field
1248 untrusted_last_seen: previous.untrusted_last_seen,
1249 // This is a wall clock time, but we already checked that responses are in order.
1250 // Even if the wall clock time has jumped, we want to use the latest time.
1251 last_response: self.last_response(local_now).or(previous.last_response),
1252 rtt: self.rtt(),
1253 ping_sent_at: self.ping_sent_at(),
1254 // These are monotonic times, we already checked the responses are in order.
1255 last_attempt: self.last_attempt(instant_now).or(previous.last_attempt),
1256 last_failure: self.last_failure(instant_now).or(previous.last_failure),
1257 // Replace the state with the updated state.
1258 last_connection_state: self.peer_addr_state(),
1259 is_inbound: previous.is_inbound || self.is_inbound(),
1260 user_agent: self.user_agent().or(previous.user_agent),
1261 negotiated_version: self.negotiated_version().or(previous.negotiated_version),
1262 })
1263 }
1264 }
1265}
1266
1267impl Ord for MetaAddr {
1268 /// `MetaAddr`s are sorted in approximate reconnection attempt order, but
1269 /// with `Responded` peers sorted first as a group.
1270 ///
1271 /// But this order should not be used for reconnection attempts: use
1272 /// [`reconnection_peers`] instead.
1273 ///
1274 /// See [`candidate_set`] for more details.
1275 ///
1276 /// [`candidate_set`]: super::peer_set::candidate_set
1277 /// [`reconnection_peers`]: crate::AddressBook::reconnection_peers
1278 fn cmp(&self, other: &Self) -> Ordering {
1279 use std::net::IpAddr::{V4, V6};
1280 use Ordering::*;
1281
1282 // First, try states that are more likely to work
1283 let more_reliable_state = self.last_connection_state.cmp(&other.last_connection_state);
1284
1285 // Then, try addresses that are more likely to be valid.
1286 // Currently, this prefers addresses with canonical Zcash ports.
1287 let more_likely_valid = self.peer_preference().cmp(&other.peer_preference());
1288
1289 // # Security and Correctness
1290 //
1291 // Prioritise older attempt times, so we try all peers in each state,
1292 // before re-trying any of them. This avoids repeatedly reconnecting to
1293 // peers that aren't working.
1294 //
1295 // Using the internal attempt time for peer ordering also minimises the
1296 // amount of information `Addrs` responses leak about Zebra's retry order.
1297
1298 // If the states are the same, try peers that we haven't tried for a while.
1299 //
1300 // Each state change updates a specific time field, and
1301 // None is less than Some(T),
1302 // so the resulting ordering for each state is:
1303 // - Responded: oldest attempts first (attempt times are required and unique)
1304 // - NeverAttempted...: recent gossiped times first (all other times are None)
1305 // - Failed: oldest attempts first (attempt times are required and unique)
1306 // - AttemptPending: oldest attempts first (attempt times are required and unique)
1307 //
1308 // We also compare the other local times, because:
1309 // - seed peers may not have an attempt time, and
1310 // - updates can be applied to the address book in any order.
1311 let older_attempt = self.last_attempt.cmp(&other.last_attempt);
1312 let older_failure = self.last_failure.cmp(&other.last_failure);
1313 let older_response = self.last_response.cmp(&other.last_response);
1314
1315 // # Security
1316 //
1317 // Compare local times before untrusted gossiped times and services.
1318 // This gives malicious peers less influence over our peer connection
1319 // order.
1320
1321 // If all local times are None, try peers that other peers have seen more recently
1322 let newer_untrusted_last_seen = self
1323 .untrusted_last_seen
1324 .cmp(&other.untrusted_last_seen)
1325 .reverse();
1326
1327 // Finally, prefer numerically larger service bit patterns
1328 //
1329 // As of June 2021, Zebra only recognises the NODE_NETWORK bit.
1330 // When making outbound connections, Zebra skips non-nodes.
1331 // So this comparison will have no impact until Zebra implements
1332 // more service features.
1333 //
1334 // None is less than Some(T), so peers with missing services are chosen last.
1335 //
1336 // TODO: order services by usefulness, not bit pattern values (#2324)
1337 // Security: split gossiped and direct services
1338 let larger_services = self.services.cmp(&other.services);
1339
1340 // The remaining comparisons are meaningless for peer connection priority.
1341 // But they are required so that we have a total order on `MetaAddr` values:
1342 // self and other must compare as Equal iff they are equal.
1343
1344 // As a tie-breaker, compare ip and port numerically
1345 //
1346 // Since SocketAddrs are unique in the address book, these comparisons
1347 // guarantee a total, unique order.
1348 let ip_tie_breaker = match (self.addr.ip(), other.addr.ip()) {
1349 (V4(a), V4(b)) => a.octets().cmp(&b.octets()),
1350 (V6(a), V6(b)) => a.octets().cmp(&b.octets()),
1351 (V4(_), V6(_)) => Less,
1352 (V6(_), V4(_)) => Greater,
1353 };
1354 let port_tie_breaker = self.addr.port().cmp(&other.addr.port());
1355
1356 more_reliable_state
1357 .then(more_likely_valid)
1358 .then(older_attempt)
1359 .then(older_failure)
1360 .then(older_response)
1361 .then(newer_untrusted_last_seen)
1362 .then(larger_services)
1363 .then(ip_tie_breaker)
1364 .then(port_tie_breaker)
1365 }
1366}
1367
1368impl PartialOrd for MetaAddr {
1369 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1370 Some(self.cmp(other))
1371 }
1372}
1373
1374impl PartialEq for MetaAddr {
1375 fn eq(&self, other: &Self) -> bool {
1376 self.cmp(other) == Ordering::Equal
1377 }
1378}
1379
1380impl Eq for MetaAddr {}