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 /// Why the peer was scored, logged if this change bans the peer's group.
340 reason: String,
341 },
342}
343
344impl MetaAddr {
345 /// Returns a [`MetaAddrChange::NewInitial`] for a peer that was excluded from
346 /// the list of the initial peers.
347 pub fn new_initial_peer(addr: PeerSocketAddr) -> MetaAddrChange {
348 NewInitial {
349 addr: canonical_peer_addr(addr),
350 }
351 }
352
353 /// Returns a new `MetaAddr`, based on the deserialized fields from a
354 /// gossiped peer [`Addr`][crate::protocol::external::Message::Addr] message.
355 pub fn new_gossiped_meta_addr(
356 addr: PeerSocketAddr,
357 untrusted_services: PeerServices,
358 untrusted_last_seen: DateTime32,
359 ) -> MetaAddr {
360 MetaAddr {
361 addr: canonical_peer_addr(addr),
362 services: Some(untrusted_services),
363 untrusted_last_seen: Some(untrusted_last_seen),
364 last_response: None,
365 rtt: None,
366 ping_sent_at: None,
367 last_attempt: None,
368 last_failure: None,
369 last_connection_state: NeverAttemptedGossiped,
370 is_inbound: false,
371 user_agent: None,
372 negotiated_version: None,
373 }
374 }
375
376 /// Returns a [`MetaAddrChange::NewGossiped`], based on a gossiped peer
377 /// [`MetaAddr`].
378 ///
379 /// Returns [`None`] if the gossiped peer is missing the untrusted services field.
380 #[allow(clippy::unwrap_in_result)]
381 pub fn new_gossiped_change(self) -> Option<MetaAddrChange> {
382 let untrusted_services = self.services?;
383
384 Some(NewGossiped {
385 addr: canonical_peer_addr(self.addr),
386 untrusted_services,
387 untrusted_last_seen: self
388 .untrusted_last_seen
389 .expect("unexpected missing last seen"),
390 })
391 }
392
393 /// Returns a [`MetaAddrChange::UpdateConnected`] for a peer that has just successfully
394 /// connected.
395 ///
396 /// # Security
397 ///
398 /// This address must be the remote address from an outbound connection,
399 /// and the services must be the services from that peer's handshake.
400 ///
401 /// Otherwise:
402 /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
403 /// state, or
404 /// - Zebra could advertise unreachable addresses to its own peers.
405 pub fn new_connected(
406 addr: PeerSocketAddr,
407 services: &PeerServices,
408 is_inbound: bool,
409 user_agent: String,
410 negotiated_version: Version,
411 ) -> MetaAddrChange {
412 UpdateConnected {
413 addr: canonical_peer_addr(*addr),
414 services: *services,
415 is_inbound,
416 user_agent,
417 negotiated_version,
418 }
419 }
420
421 /// Returns a [`MetaAddrChange::UpdatePingSent`] for a peer that we just sent a ping to.
422 pub fn new_ping_sent(addr: PeerSocketAddr, ping_sent_at: Instant) -> MetaAddrChange {
423 UpdatePingSent {
424 addr: canonical_peer_addr(*addr),
425 ping_sent_at,
426 }
427 }
428
429 /// Returns a [`MetaAddrChange::UpdateResponded`] for a peer that has just
430 /// sent us a message.
431 ///
432 /// # Security
433 ///
434 /// This address must be the remote address from an outbound connection.
435 ///
436 /// Otherwise:
437 /// - malicious peers could interfere with other peers' [`AddressBook`](crate::AddressBook)
438 /// state, or
439 /// - Zebra could advertise unreachable addresses to its own peers.
440 pub fn new_responded(addr: PeerSocketAddr, rtt: Option<Duration>) -> MetaAddrChange {
441 UpdateResponded {
442 addr: canonical_peer_addr(*addr),
443 rtt,
444 }
445 }
446
447 /// Returns a [`MetaAddrChange::UpdateAttempt`] for a peer that we
448 /// want to make an outbound connection to.
449 pub fn new_reconnect(addr: PeerSocketAddr) -> MetaAddrChange {
450 UpdateAttempt {
451 addr: canonical_peer_addr(*addr),
452 }
453 }
454
455 /// Returns a [`MetaAddrChange::NewLocal`] for our own listener address.
456 pub fn new_local_listener_change(addr: impl Into<PeerSocketAddr>) -> MetaAddrChange {
457 NewLocal {
458 addr: canonical_peer_addr(addr),
459 }
460 }
461
462 /// Returns a [`MetaAddrChange::UpdateFailed`] for a peer that has just had an error.
463 pub fn new_errored(
464 addr: PeerSocketAddr,
465 services: impl Into<Option<PeerServices>>,
466 ) -> MetaAddrChange {
467 UpdateFailed {
468 addr: canonical_peer_addr(*addr),
469 services: services.into(),
470 }
471 }
472
473 /// Returns a [`MetaAddrChange::UpdateMisbehavior`] for a peer that has misbehaved.
474 ///
475 /// Canonicalizes the address to match the form stored by a successful handshake
476 /// (`new_connected`). On Linux dual-stack sockets, inbound IPv4 connections
477 /// arrive as IPv4-mapped IPv6 addresses (`::ffff:A.B.C.D`); without
478 /// canonicalization, `apply_to_meta_addr` panics on the addr invariant.
479 pub fn new_misbehavior(
480 addr: PeerSocketAddr,
481 score_increment: u32,
482 reason: impl Into<String>,
483 ) -> MetaAddrChange {
484 UpdateMisbehavior {
485 addr: canonical_peer_addr(*addr),
486 score_increment,
487 reason: reason.into(),
488 }
489 }
490
491 /// Create a new `MetaAddr` for a peer that has just shut down.
492 pub fn new_shutdown(addr: PeerSocketAddr) -> MetaAddrChange {
493 // TODO: if the peer shut down in the Responded state, preserve that
494 // state. All other states should be treated as (timeout) errors.
495 MetaAddr::new_errored(addr, None)
496 }
497
498 /// Return the address for this `MetaAddr`.
499 pub fn addr(&self) -> PeerSocketAddr {
500 self.addr
501 }
502
503 /// Return the address preference level for this `MetaAddr`.
504 pub fn peer_preference(&self) -> Result<PeerPreference, &'static str> {
505 PeerPreference::new(self.addr, None)
506 }
507
508 /// Returns the time of the last successful interaction with this peer.
509 ///
510 /// Initially set to the unverified "last seen time" gossiped by the remote
511 /// peer that sent us this address.
512 ///
513 /// If the `last_connection_state` has ever been `Responded`, this field is
514 /// set to the last time we processed a message from this peer.
515 ///
516 /// ## Security
517 ///
518 /// `last_seen` times from peers that have never `Responded` may be
519 /// incorrect due to clock skew, or buggy or malicious peers.
520 pub fn last_seen(&self) -> Option<DateTime32> {
521 self.last_response.or(self.untrusted_last_seen)
522 }
523
524 /// Returns whether the address is from an inbound peer connection
525 pub fn is_inbound(&self) -> bool {
526 self.is_inbound
527 }
528
529 /// Returns the round-trip time (RTT) for this peer, if available.
530 pub fn rtt(&self) -> Option<Duration> {
531 self.rtt
532 }
533
534 /// Returns the time this peer was last pinged, if available.
535 pub fn ping_sent_at(&self) -> Option<Instant> {
536 self.ping_sent_at
537 }
538
539 /// Returns the unverified "last seen time" gossiped by the remote peer that
540 /// sent us this address.
541 ///
542 /// See the [`MetaAddr::last_seen`] method for details.
543 //
544 // TODO: pub(in crate::address_book) - move meta_addr into address_book
545 pub(crate) fn untrusted_last_seen(&self) -> Option<DateTime32> {
546 self.untrusted_last_seen
547 }
548
549 /// Returns the last time we received a message from this peer.
550 ///
551 /// See the [`MetaAddr::last_seen`] method for details.
552 //
553 // TODO: pub(in crate::address_book) - move meta_addr into address_book
554 #[allow(dead_code)]
555 pub(crate) fn last_response(&self) -> Option<DateTime32> {
556 self.last_response
557 }
558
559 /// Set the gossiped untrusted last seen time for this peer.
560 pub(crate) fn set_untrusted_last_seen(&mut self, untrusted_last_seen: DateTime32) {
561 self.untrusted_last_seen = Some(untrusted_last_seen);
562 }
563
564 /// Returns the time of our last outbound connection attempt with this peer.
565 ///
566 /// If the `last_connection_state` has ever been `AttemptPending`, this
567 /// field is set to the last time we started an outbound connection attempt
568 /// with this peer.
569 pub fn last_attempt(&self) -> Option<Instant> {
570 self.last_attempt
571 }
572
573 /// Returns the time of our last failed outbound connection with this peer.
574 ///
575 /// If the `last_connection_state` has ever been `Failed`, this field is set
576 /// to the last time:
577 /// - a connection attempt failed, or
578 /// - an open connection encountered a fatal protocol error.
579 pub fn last_failure(&self) -> Option<Instant> {
580 self.last_failure
581 }
582
583 /// Have we had any recently messages from this peer?
584 ///
585 /// Returns `true` if the peer is likely connected and responsive in the peer
586 /// set.
587 ///
588 /// [`constants::MIN_PEER_RECONNECTION_DELAY`] represents the time interval in which
589 /// we should receive at least one message from a peer, or close the
590 /// connection. Therefore, if the last-seen timestamp is older than
591 /// [`constants::MIN_PEER_RECONNECTION_DELAY`] ago, we know we should have
592 /// disconnected from it. Otherwise, we could potentially be connected to it.
593 pub fn has_connection_recently_responded(&self, now: chrono::DateTime<Utc>) -> bool {
594 if let Some(last_response) = self.last_response {
595 // Recent times and future times are considered live
596 last_response.saturating_elapsed(now)
597 <= constants::MIN_PEER_RECONNECTION_DELAY
598 .try_into()
599 .expect("unexpectedly large constant")
600 } else {
601 // If there has never been any response, it can't possibly be live
602 false
603 }
604 }
605
606 /// Have we recently attempted an outbound connection to this peer?
607 ///
608 /// Returns `true` if this peer was recently attempted, or has a connection
609 /// attempt in progress.
610 pub fn was_connection_recently_attempted(&self, now: Instant) -> bool {
611 if let Some(last_attempt) = self.last_attempt {
612 // Recent times and future times are considered live.
613 // Instants are monotonic, so `now` should always be later than `last_attempt`,
614 // except for synthetic data in tests.
615 now.saturating_duration_since(last_attempt) <= constants::MIN_PEER_RECONNECTION_DELAY
616 } else {
617 // If there has never been any attempt, it can't possibly be live
618 false
619 }
620 }
621
622 /// Have we recently had a failed connection to this peer?
623 ///
624 /// Returns `true` if this peer has recently failed.
625 pub fn has_connection_recently_failed(&self, now: Instant) -> bool {
626 if let Some(last_failure) = self.last_failure {
627 // Recent times and future times are considered live
628 now.saturating_duration_since(last_failure) <= constants::MIN_PEER_RECONNECTION_DELAY
629 } else {
630 // If there has never been any failure, it can't possibly be recent
631 false
632 }
633 }
634
635 /// Returns true if this peer has recently sent us a message.
636 pub fn was_recently_live(&self, now: chrono::DateTime<Utc>) -> bool {
637 // NeverAttempted, Failed, and AttemptPending peers should never be live
638 self.last_connection_state == PeerAddrState::Responded
639 && self.has_connection_recently_responded(now)
640 }
641
642 /// Has this peer been seen recently?
643 ///
644 /// Returns `true` if this peer has responded recently or if the peer was gossiped with a
645 /// recent reported last seen time.
646 ///
647 /// [`constants::MAX_PEER_ACTIVE_FOR_GOSSIP`] represents the maximum time since a peer was seen
648 /// to still be considered reachable.
649 pub fn is_active_for_gossip(&self, now: chrono::DateTime<Utc>) -> bool {
650 if let Some(last_seen) = self.last_seen() {
651 // Correctness: `last_seen` shouldn't ever be in the future, either because we set the
652 // time or because another peer's future time was sanitized when it was added to the
653 // address book
654 last_seen.saturating_elapsed(now) <= constants::MAX_PEER_ACTIVE_FOR_GOSSIP
655 } else {
656 // Peer has never responded and does not have a gossiped last seen time
657 false
658 }
659 }
660
661 /// Returns true if any messages were recently sent to or received from this address.
662 pub fn was_recently_updated(
663 &self,
664 instant_now: Instant,
665 chrono_now: chrono::DateTime<Utc>,
666 ) -> bool {
667 self.has_connection_recently_responded(chrono_now)
668 || self.was_connection_recently_attempted(instant_now)
669 || self.has_connection_recently_failed(instant_now)
670 }
671
672 /// Is this address ready for a new outbound connection attempt?
673 pub fn is_ready_for_connection_attempt(
674 &self,
675 instant_now: Instant,
676 chrono_now: chrono::DateTime<Utc>,
677 network: &Network,
678 ) -> bool {
679 self.last_known_info_is_valid_for_outbound(network)
680 && !self.was_recently_updated(instant_now, chrono_now)
681 && self.is_probably_reachable(chrono_now)
682 }
683
684 /// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
685 /// connections?
686 ///
687 /// Since the addresses in the address book are unique, this check can be
688 /// used to permanently reject entire [`MetaAddr`]s.
689 pub fn address_is_valid_for_outbound(&self, network: &Network) -> bool {
690 address_is_valid_for_outbound_connections(self.addr, network.clone()).is_ok()
691 }
692
693 /// Is the last known information for this peer valid for outbound
694 /// connections?
695 ///
696 /// The last known info might be outdated or untrusted, so this check can
697 /// only be used to:
698 /// - reject `NeverAttempted...` [`MetaAddrChange`]s, and
699 /// - temporarily stop outbound connections to a [`MetaAddr`].
700 pub fn last_known_info_is_valid_for_outbound(&self, network: &Network) -> bool {
701 let is_node = match self.services {
702 Some(services) => services.contains(PeerServices::NODE_NETWORK),
703 None => true,
704 };
705
706 is_node && self.address_is_valid_for_outbound(network)
707 }
708
709 /// Should this peer considered reachable?
710 ///
711 /// A peer is probably reachable if:
712 /// - it has never been attempted, or
713 /// - the last connection attempt was successful, or
714 /// - the last successful connection was less than 3 days ago.
715 ///
716 /// # Security
717 ///
718 /// This is used by [`Self::is_ready_for_connection_attempt`] so that Zebra stops trying to
719 /// connect to peers that are likely unreachable.
720 ///
721 /// The `untrusted_last_seen` time is used as a fallback time if the local node has never
722 /// itself seen the peer. If the reported last seen time is a long time ago or `None`, then the local
723 /// node will attempt to connect the peer once, and if that attempt fails it won't
724 /// try to connect ever again. (The state can't be `Failed` until after the first connection attempt.)
725 pub fn is_probably_reachable(&self, now: chrono::DateTime<Utc>) -> bool {
726 self.last_connection_state != PeerAddrState::Failed || self.last_seen_is_recent(now)
727 }
728
729 /// Was this peer last seen recently?
730 ///
731 /// Returns `true` if this peer was last seen at most
732 /// [`MAX_RECENT_PEER_AGE`][constants::MAX_RECENT_PEER_AGE] ago.
733 /// Returns false if the peer is outdated, or it has no last seen time.
734 pub fn last_seen_is_recent(&self, now: chrono::DateTime<Utc>) -> bool {
735 match self.last_seen() {
736 Some(last_seen) => last_seen.saturating_elapsed(now) <= constants::MAX_RECENT_PEER_AGE,
737 None => false,
738 }
739 }
740
741 /// Returns the services advertised by the peer, if available.
742 pub fn services(&self) -> Option<PeerServices> {
743 self.services
744 }
745
746 /// Returns the last known connection state for this peer.
747 pub fn last_connection_state(&self) -> PeerAddrState {
748 self.last_connection_state
749 }
750
751 /// Returns the user agent string reported by this peer, if available.
752 pub fn user_agent(&self) -> Option<&str> {
753 self.user_agent.as_deref()
754 }
755
756 /// Returns the negotiated protocol version for this peer, if available.
757 pub fn negotiated_version(&self) -> Option<Version> {
758 self.negotiated_version
759 }
760
761 /// Return a sanitized version of this `MetaAddr`, for sending to a remote peer.
762 ///
763 /// Returns `None` if this `MetaAddr` should not be sent to remote peers.
764 #[allow(clippy::unwrap_in_result)]
765 pub fn sanitize(&self, network: &Network) -> Option<MetaAddr> {
766 if !self.last_known_info_is_valid_for_outbound(network) {
767 return None;
768 }
769
770 // Inbound addresses are ephemeral remote ports, not listener ports.
771 if self.is_inbound {
772 return None;
773 }
774
775 // Sanitize time
776 let last_seen = self.last_seen()?;
777 let remainder = last_seen
778 .timestamp()
779 .rem_euclid(crate::constants::TIMESTAMP_TRUNCATION_SECONDS);
780 let last_seen = last_seen
781 .checked_sub(remainder.into())
782 .expect("unexpected underflow: rem_euclid is strictly less than timestamp");
783
784 Some(MetaAddr {
785 addr: canonical_peer_addr(self.addr),
786 // initial peers are sanitized assuming they are `NODE_NETWORK`
787 // TODO: split untrusted and direct services
788 // consider sanitizing untrusted services to NODE_NETWORK (#2324)
789 services: self.services.or(Some(PeerServices::NODE_NETWORK)),
790 // only put the last seen time in the untrusted field,
791 // this matches deserialization, and avoids leaking internal state
792 untrusted_last_seen: Some(last_seen),
793 last_response: None,
794 // these fields aren't sent to the remote peer, but sanitize them anyway
795 rtt: None,
796 ping_sent_at: None,
797 last_attempt: None,
798 last_failure: None,
799 last_connection_state: NeverAttemptedGossiped,
800 is_inbound: false,
801 user_agent: None,
802 negotiated_version: None,
803 })
804 }
805}
806
807#[cfg(test)]
808impl MetaAddr {
809 /// Forcefully change the time this peer last responded.
810 ///
811 /// This method is for testing purposes only.
812 pub(crate) fn set_last_response(&mut self, last_response: DateTime32) {
813 self.last_response = Some(last_response);
814 }
815}
816
817impl MetaAddrChange {
818 /// Return the address for this change.
819 pub fn addr(&self) -> PeerSocketAddr {
820 match self {
821 NewInitial { addr }
822 | NewGossiped { addr, .. }
823 | NewLocal { addr, .. }
824 | UpdateAttempt { addr }
825 | UpdateConnected { addr, .. }
826 | UpdatePingSent { addr, .. }
827 | UpdateResponded { addr, .. }
828 | UpdateFailed { addr, .. }
829 | UpdateMisbehavior { addr, .. } => *addr,
830 }
831 }
832
833 #[cfg(any(test, feature = "proptest-impl"))]
834 /// Set the address for this change to `new_addr`.
835 ///
836 /// This method should only be used in tests.
837 pub fn set_addr(&mut self, new_addr: PeerSocketAddr) {
838 match self {
839 NewInitial { addr }
840 | NewGossiped { addr, .. }
841 | NewLocal { addr, .. }
842 | UpdateAttempt { addr }
843 | UpdateConnected { addr, .. }
844 | UpdatePingSent { addr, .. }
845 | UpdateResponded { addr, .. }
846 | UpdateFailed { addr, .. }
847 | UpdateMisbehavior { addr, .. } => *addr = new_addr,
848 }
849 }
850
851 /// Return the untrusted services for this change, if available.
852 pub fn untrusted_services(&self) -> Option<PeerServices> {
853 match self {
854 NewInitial { .. } => None,
855 // TODO: split untrusted and direct services (#2324)
856 NewGossiped {
857 untrusted_services, ..
858 } => Some(*untrusted_services),
859 // TODO: create a "services implemented by Zebra" constant (#2324)
860 NewLocal { .. } => Some(PeerServices::NODE_NETWORK),
861 UpdateAttempt { .. } => None,
862 UpdateConnected { services, .. } => Some(*services),
863 UpdatePingSent { .. } => None,
864 UpdateResponded { .. } => None,
865 UpdateFailed { services, .. } => *services,
866 UpdateMisbehavior { .. } => None,
867 }
868 }
869
870 /// Return the untrusted last seen time for this change, if available.
871 pub fn untrusted_last_seen(&self, now: DateTime32) -> Option<DateTime32> {
872 match self {
873 NewInitial { .. } => None,
874 NewGossiped {
875 untrusted_last_seen,
876 ..
877 } => Some(*untrusted_last_seen),
878 // We know that our local listener is available
879 NewLocal { .. } => Some(now),
880 UpdateAttempt { .. }
881 | UpdateConnected { .. }
882 | UpdatePingSent { .. }
883 | UpdateResponded { .. }
884 | UpdateFailed { .. }
885 | UpdateMisbehavior { .. } => None,
886 }
887 }
888
889 // # Concurrency
890 //
891 // We assign a time to each change when it is applied to the address book by either the
892 // address book updater or candidate set tasks. This is the time that the change was received
893 // from the updater channel, rather than the time that the message was read from the peer
894 // connection.
895 //
896 // Since the connection tasks run concurrently in an unspecified order, and the address book
897 // updater runs in a separate thread, these times are almost always very similar. If Zebra's
898 // address book is under load, we should use lower rate-limits for new inbound or outbound
899 // connections, disconnections, peer gossip crawls, or peer `UpdateResponded` updates.
900 //
901 // TODO:
902 // - move the time API calls from `impl MetaAddrChange` `last_*()` methods:
903 // - if they impact performance, call them once in the address book updater task,
904 // then apply them to all the waiting changes
905 // - otherwise, move them to the `impl MetaAddrChange` `new_*()` methods,
906 // so they are called in the connection tasks
907 //
908 /// Return the last attempt for this change, if available.
909 pub fn last_attempt(&self, now: Instant) -> Option<Instant> {
910 match self {
911 NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } => None,
912 // Attempt changes are applied before we start the handshake to the
913 // peer address. So the attempt time is a lower bound for the actual
914 // handshake time.
915 UpdateAttempt { .. } => Some(now),
916 UpdateConnected { .. }
917 | UpdatePingSent { .. }
918 | UpdateResponded { .. }
919 | UpdateFailed { .. }
920 | UpdateMisbehavior { .. } => None,
921 }
922 }
923
924 /// Return the last response for this change, if available.
925 pub fn last_response(&self, now: DateTime32) -> Option<DateTime32> {
926 match self {
927 NewInitial { .. } | NewGossiped { .. } | NewLocal { .. } | UpdateAttempt { .. } => None,
928 // If there is a large delay applying this change, then:
929 // - the peer might stay in the `AttemptPending` state for longer,
930 // - we might send outdated last seen times to our peers, and
931 // - the peer will appear to be live for longer, delaying future
932 // reconnection attempts.
933 UpdateConnected { .. } | UpdateResponded { .. } => Some(now),
934 UpdateFailed { .. } | UpdateMisbehavior { .. } => None,
935 UpdatePingSent { .. } => None,
936 }
937 }
938
939 /// Return the timestamp when a ping was last sent, if available.
940 pub fn ping_sent(&self) -> Option<Instant> {
941 match self {
942 UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
943 _ => None,
944 }
945 }
946
947 /// Return the RTT for this change, if available
948 pub fn rtt(&self) -> Option<Duration> {
949 match self {
950 UpdateResponded { rtt, .. } => *rtt,
951 _ => None,
952 }
953 }
954
955 /// Returns the timestamp when a ping was last sent, if available.
956 pub fn ping_sent_at(&self) -> Option<Instant> {
957 match self {
958 UpdatePingSent { ping_sent_at, .. } => Some(*ping_sent_at),
959 _ => None,
960 }
961 }
962
963 /// Return the last failure for this change, if available.
964 pub fn last_failure(&self, now: Instant) -> Option<Instant> {
965 match self {
966 NewInitial { .. }
967 | NewGossiped { .. }
968 | NewLocal { .. }
969 | UpdateAttempt { .. }
970 | UpdateConnected { .. }
971 | UpdatePingSent { .. }
972 | UpdateResponded { .. } => None,
973 // If there is a large delay applying this change, then:
974 // - the peer might stay in the `AttemptPending` or `Responded`
975 // states for longer, and
976 // - the peer will appear to be used for longer, delaying future
977 // reconnection attempts.
978 UpdateFailed { .. } | UpdateMisbehavior { .. } => Some(now),
979 }
980 }
981
982 /// Return the peer connection state for this change.
983 pub fn peer_addr_state(&self) -> PeerAddrState {
984 match self {
985 NewInitial { .. } => NeverAttemptedGossiped,
986 NewGossiped { .. } => NeverAttemptedGossiped,
987 // local listeners get sanitized, so the state doesn't matter here
988 NewLocal { .. } => NeverAttemptedGossiped,
989 UpdateAttempt { .. } => AttemptPending,
990 UpdateConnected { .. }
991 // Sending a ping is an interaction with a connected peer, but does not indicate new liveness.
992 // Peers stay in Responded once connected, so we keep them in that state for UpdatePingSent.
993 | UpdatePingSent { .. }
994 | UpdateResponded { .. }
995 | UpdateMisbehavior { .. } => Responded,
996 UpdateFailed { .. } => Failed,
997 }
998 }
999
1000 /// Returns the corresponding `MetaAddr` for this change.
1001 pub fn into_new_meta_addr(self, instant_now: Instant, local_now: DateTime32) -> MetaAddr {
1002 let user_agent = self.user_agent();
1003 let negotiated_version = self.negotiated_version();
1004 MetaAddr {
1005 addr: self.addr(),
1006 services: self.untrusted_services(),
1007 untrusted_last_seen: self.untrusted_last_seen(local_now),
1008 last_response: self.last_response(local_now),
1009 rtt: self.rtt(),
1010 ping_sent_at: self.ping_sent_at(),
1011 last_attempt: self.last_attempt(instant_now),
1012 last_failure: self.last_failure(instant_now),
1013 last_connection_state: self.peer_addr_state(),
1014 is_inbound: self.is_inbound(),
1015 user_agent,
1016 negotiated_version,
1017 }
1018 }
1019
1020 /// Returns the misbehavior score for the current change, or `0` if it is not
1021 /// an [`MetaAddrChange::UpdateMisbehavior`] change.
1022 pub fn misbehavior_score(&self) -> u32 {
1023 match self {
1024 MetaAddrChange::UpdateMisbehavior {
1025 score_increment, ..
1026 } => *score_increment,
1027 _ => 0,
1028 }
1029 }
1030
1031 /// Returns the reason the peer was scored, or `None` if this is not an
1032 /// [`MetaAddrChange::UpdateMisbehavior`] change.
1033 pub fn misbehavior_reason(&self) -> Option<&str> {
1034 match self {
1035 MetaAddrChange::UpdateMisbehavior { reason, .. } => Some(reason),
1036 _ => None,
1037 }
1038 }
1039
1040 /// Returns whether this change was created for a new inbound connection.
1041 pub fn is_inbound(&self) -> bool {
1042 if let MetaAddrChange::UpdateConnected { is_inbound, .. } = self {
1043 *is_inbound
1044 } else {
1045 false
1046 }
1047 }
1048
1049 /// Returns the user agent from this change, if available.
1050 pub fn user_agent(&self) -> Option<String> {
1051 if let MetaAddrChange::UpdateConnected { user_agent, .. } = self {
1052 Some(user_agent.clone())
1053 } else {
1054 None
1055 }
1056 }
1057
1058 /// Returns the negotiated protocol version from this change, if available.
1059 pub fn negotiated_version(&self) -> Option<Version> {
1060 if let MetaAddrChange::UpdateConnected {
1061 negotiated_version, ..
1062 } = self
1063 {
1064 Some(*negotiated_version)
1065 } else {
1066 None
1067 }
1068 }
1069
1070 /// Returns the corresponding [`MetaAddr`] for a local listener change.
1071 ///
1072 /// This method exists so we don't have to provide an unused [`Instant`] to get a local
1073 /// listener `MetaAddr`.
1074 ///
1075 /// # Panics
1076 ///
1077 /// If this change is not a [`MetaAddrChange::NewLocal`].
1078 pub fn local_listener_into_new_meta_addr(self, local_now: DateTime32) -> MetaAddr {
1079 assert!(matches!(self, MetaAddrChange::NewLocal { .. }));
1080
1081 MetaAddr {
1082 addr: self.addr(),
1083 services: self.untrusted_services(),
1084 untrusted_last_seen: self.untrusted_last_seen(local_now),
1085 last_response: self.last_response(local_now),
1086 rtt: None,
1087 ping_sent_at: None,
1088 last_attempt: None,
1089 last_failure: None,
1090 last_connection_state: self.peer_addr_state(),
1091 is_inbound: self.is_inbound(),
1092 user_agent: None,
1093 negotiated_version: None,
1094 }
1095 }
1096
1097 /// Apply this change to a previous `MetaAddr` from the address book,
1098 /// producing a new or updated `MetaAddr`.
1099 ///
1100 /// If the change isn't valid for the `previous` address, returns `None`.
1101 #[allow(clippy::unwrap_in_result)]
1102 pub fn apply_to_meta_addr(
1103 &self,
1104 previous: impl Into<Option<MetaAddr>>,
1105 instant_now: Instant,
1106 chrono_now: chrono::DateTime<Utc>,
1107 ) -> Option<MetaAddr> {
1108 let local_now: DateTime32 = chrono_now.try_into().expect("will succeed until 2038");
1109
1110 let Some(previous) = previous.into() else {
1111 // no previous: create a new entry
1112 return Some(self.clone().into_new_meta_addr(instant_now, local_now));
1113 };
1114
1115 assert_eq!(previous.addr, self.addr(), "unexpected addr mismatch");
1116
1117 let instant_previous = max(previous.last_attempt, previous.last_failure);
1118 let local_previous = previous.last_response;
1119
1120 // Is this change potentially concurrent with the previous change?
1121 //
1122 // Since we're using saturating arithmetic, one of each pair of less than comparisons
1123 // will always be true, because subtraction saturates to zero.
1124 let change_is_concurrent = instant_previous
1125 .map(|instant_previous| {
1126 instant_previous.saturating_duration_since(instant_now)
1127 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1128 && instant_now.saturating_duration_since(instant_previous)
1129 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1130 })
1131 .unwrap_or_default()
1132 || local_previous
1133 .map(|local_previous| {
1134 local_previous.saturating_duration_since(local_now).to_std()
1135 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1136 && local_now.saturating_duration_since(local_previous).to_std()
1137 < constants::CONCURRENT_ADDRESS_CHANGE_PERIOD
1138 })
1139 .unwrap_or_default();
1140 let change_is_out_of_order = instant_previous
1141 .map(|instant_previous| instant_previous > instant_now)
1142 .unwrap_or_default()
1143 || local_previous
1144 .map(|local_previous| local_previous > local_now)
1145 .unwrap_or_default();
1146
1147 // Is this change typically from a connection state that has more progress?
1148 let connection_has_more_progress = self
1149 .peer_addr_state()
1150 .connection_state_order(&previous.last_connection_state)
1151 == Ordering::Greater;
1152
1153 let previous_has_been_attempted = !previous.last_connection_state.is_never_attempted();
1154 let change_to_never_attempted = self.peer_addr_state().is_never_attempted();
1155 let is_misbehavior_update = self.misbehavior_score() != 0;
1156
1157 // Invalid changes
1158
1159 if change_to_never_attempted && previous_has_been_attempted && !is_misbehavior_update {
1160 // Existing entry has been attempted, change is NeverAttempted
1161 // - ignore the change
1162 //
1163 // # Security
1164 //
1165 // Ignore NeverAttempted changes once we have made an attempt,
1166 // so malicious peers can't keep changing our peer connection order.
1167 return None;
1168 }
1169
1170 if change_is_out_of_order && !change_is_concurrent && !is_misbehavior_update {
1171 // Change is significantly out of order: ignore it.
1172 //
1173 // # Security
1174 //
1175 // Ignore changes that arrive out of order, if they are far enough apart.
1176 // This enforces the peer connection retry interval.
1177 return None;
1178 }
1179
1180 if change_is_concurrent && !connection_has_more_progress && !is_misbehavior_update {
1181 // Change is close together in time, and it would revert the connection to an earlier
1182 // state.
1183 //
1184 // # Security
1185 //
1186 // If the changes might have been concurrent, ignore connection states with less
1187 // progress.
1188 //
1189 // ## Sources of Concurrency
1190 //
1191 // If two changes happen close together, the async scheduler can run their change
1192 // send and apply code in any order. This includes the code that records the time of
1193 // the change. So even if a failure happens after a response message, the failure time
1194 // can be recorded before the response time code is run.
1195 //
1196 // Some machines and OSes have limited time resolution, so we can't guarantee that
1197 // two messages on the same connection will always have different times. There are
1198 // also known bugs impacting monotonic times which make them go backwards or stay
1199 // equal. For wall clock times, clock skew is an expected event, particularly with
1200 // network time server updates.
1201 //
1202 // Also, the application can fail a connection independently and simultaneously
1203 // (or slightly before) a positive update from that peer connection. We want the
1204 // application change to take priority in the address book, because the connection
1205 // state machine also prioritises failures over any other peer messages.
1206 //
1207 // ## Resolution
1208 //
1209 // In these cases, we want to apply the failure, then ignore any nearby changes that
1210 // reset the address book entry to a more appealing state. This prevents peers from
1211 // sending updates right before failing a connection, in order to make themselves more
1212 // likely to get a reconnection.
1213 //
1214 // The connection state machine order is used so that state transitions which are
1215 // typically close together are preserved. These transitions are:
1216 // - NeverAttempted*->AttemptPending->(Responded|Failed)
1217 // - Responded->Failed
1218 //
1219 // State transitions like (Responded|Failed)->AttemptPending only happen after the
1220 // reconnection timeout, so they will never be considered concurrent.
1221 return None;
1222 }
1223
1224 // Valid changes
1225
1226 if change_to_never_attempted && !previous_has_been_attempted {
1227 // Existing entry and change are both NeverAttempted
1228 // - preserve original values of all fields
1229 // - but replace None with Some
1230 //
1231 // # Security
1232 //
1233 // Preserve the original field values for NeverAttempted peers,
1234 // so malicious peers can't keep changing our peer connection order.
1235 Some(MetaAddr {
1236 addr: self.addr(),
1237 services: previous.services.or_else(|| self.untrusted_services()),
1238 untrusted_last_seen: previous
1239 .untrusted_last_seen
1240 .or_else(|| self.untrusted_last_seen(local_now)),
1241 // The peer has not been attempted, so these fields must be None
1242 last_response: None,
1243 rtt: None,
1244 ping_sent_at: None,
1245 last_attempt: None,
1246 last_failure: None,
1247 last_connection_state: self.peer_addr_state(),
1248 is_inbound: previous.is_inbound || self.is_inbound(),
1249 user_agent: None,
1250 negotiated_version: None,
1251 })
1252 } else {
1253 // Existing entry and change are both Attempt, Responded, Failed,
1254 // and the change is later, either in time or in connection progress
1255 // (this is checked above and returns None early):
1256 // - update the fields from the change
1257 Some(MetaAddr {
1258 addr: self.addr(),
1259 // Always update optional fields, unless the update is None.
1260 //
1261 // We want up-to-date services, even if they have fewer bits
1262 services: self.untrusted_services().or(previous.services),
1263 // Only NeverAttempted changes can modify the last seen field
1264 untrusted_last_seen: previous.untrusted_last_seen,
1265 // This is a wall clock time, but we already checked that responses are in order.
1266 // Even if the wall clock time has jumped, we want to use the latest time.
1267 last_response: self.last_response(local_now).or(previous.last_response),
1268 rtt: self.rtt(),
1269 ping_sent_at: self.ping_sent_at(),
1270 // These are monotonic times, we already checked the responses are in order.
1271 last_attempt: self.last_attempt(instant_now).or(previous.last_attempt),
1272 last_failure: self.last_failure(instant_now).or(previous.last_failure),
1273 // Replace the state with the updated state.
1274 last_connection_state: self.peer_addr_state(),
1275 is_inbound: previous.is_inbound || self.is_inbound(),
1276 user_agent: self.user_agent().or(previous.user_agent),
1277 negotiated_version: self.negotiated_version().or(previous.negotiated_version),
1278 })
1279 }
1280 }
1281}
1282
1283impl Ord for MetaAddr {
1284 /// `MetaAddr`s are sorted in approximate reconnection attempt order, but
1285 /// with `Responded` peers sorted first as a group.
1286 ///
1287 /// But this order should not be used for reconnection attempts: use
1288 /// [`reconnection_peers`] instead.
1289 ///
1290 /// See [`candidate_set`] for more details.
1291 ///
1292 /// [`candidate_set`]: super::peer_set::candidate_set
1293 /// [`reconnection_peers`]: crate::AddressBook::reconnection_peers
1294 fn cmp(&self, other: &Self) -> Ordering {
1295 use std::net::IpAddr::{V4, V6};
1296 use Ordering::*;
1297
1298 // First, try states that are more likely to work
1299 let more_reliable_state = self.last_connection_state.cmp(&other.last_connection_state);
1300
1301 // Then, try addresses that are more likely to be valid.
1302 // Currently, this prefers addresses with canonical Zcash ports.
1303 let more_likely_valid = self.peer_preference().cmp(&other.peer_preference());
1304
1305 // # Security and Correctness
1306 //
1307 // Prioritise older attempt times, so we try all peers in each state,
1308 // before re-trying any of them. This avoids repeatedly reconnecting to
1309 // peers that aren't working.
1310 //
1311 // Using the internal attempt time for peer ordering also minimises the
1312 // amount of information `Addrs` responses leak about Zebra's retry order.
1313
1314 // If the states are the same, try peers that we haven't tried for a while.
1315 //
1316 // Each state change updates a specific time field, and
1317 // None is less than Some(T),
1318 // so the resulting ordering for each state is:
1319 // - Responded: oldest attempts first (attempt times are required and unique)
1320 // - NeverAttempted...: recent gossiped times first (all other times are None)
1321 // - Failed: oldest attempts first (attempt times are required and unique)
1322 // - AttemptPending: oldest attempts first (attempt times are required and unique)
1323 //
1324 // We also compare the other local times, because:
1325 // - seed peers may not have an attempt time, and
1326 // - updates can be applied to the address book in any order.
1327 let older_attempt = self.last_attempt.cmp(&other.last_attempt);
1328 let older_failure = self.last_failure.cmp(&other.last_failure);
1329 let older_response = self.last_response.cmp(&other.last_response);
1330
1331 // # Security
1332 //
1333 // Compare local times before untrusted gossiped times and services.
1334 // This gives malicious peers less influence over our peer connection
1335 // order.
1336
1337 // If all local times are None, try peers that other peers have seen more recently
1338 let newer_untrusted_last_seen = self
1339 .untrusted_last_seen
1340 .cmp(&other.untrusted_last_seen)
1341 .reverse();
1342
1343 // Finally, prefer numerically larger service bit patterns
1344 //
1345 // As of June 2021, Zebra only recognises the NODE_NETWORK bit.
1346 // When making outbound connections, Zebra skips non-nodes.
1347 // So this comparison will have no impact until Zebra implements
1348 // more service features.
1349 //
1350 // None is less than Some(T), so peers with missing services are chosen last.
1351 //
1352 // TODO: order services by usefulness, not bit pattern values (#2324)
1353 // Security: split gossiped and direct services
1354 let larger_services = self.services.cmp(&other.services);
1355
1356 // The remaining comparisons are meaningless for peer connection priority.
1357 // But they are required so that we have a total order on `MetaAddr` values:
1358 // self and other must compare as Equal iff they are equal.
1359
1360 // As a tie-breaker, compare ip and port numerically
1361 //
1362 // Since SocketAddrs are unique in the address book, these comparisons
1363 // guarantee a total, unique order.
1364 let ip_tie_breaker = match (self.addr.ip(), other.addr.ip()) {
1365 (V4(a), V4(b)) => a.octets().cmp(&b.octets()),
1366 (V6(a), V6(b)) => a.octets().cmp(&b.octets()),
1367 (V4(_), V6(_)) => Less,
1368 (V6(_), V4(_)) => Greater,
1369 };
1370 let port_tie_breaker = self.addr.port().cmp(&other.addr.port());
1371
1372 more_reliable_state
1373 .then(more_likely_valid)
1374 .then(older_attempt)
1375 .then(older_failure)
1376 .then(older_response)
1377 .then(newer_untrusted_last_seen)
1378 .then(larger_services)
1379 .then(ip_tie_breaker)
1380 .then(port_tie_breaker)
1381 }
1382}
1383
1384impl PartialOrd for MetaAddr {
1385 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1386 Some(self.cmp(other))
1387 }
1388}
1389
1390impl PartialEq for MetaAddr {
1391 fn eq(&self, other: &Self) -> bool {
1392 self.cmp(other) == Ordering::Equal
1393 }
1394}
1395
1396impl Eq for MetaAddr {}