Skip to main content

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