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