2019-09-12 03:36:50 -07:00
//! An address-with-metadata type used in Bitcoin networking.
2019-10-17 21:19:23 -07:00
use std ::{
cmp ::{ Ord , Ordering } ,
2021-05-17 13:53:10 -07:00
convert ::TryInto ,
2019-10-17 21:19:23 -07:00
io ::{ Read , Write } ,
net ::SocketAddr ,
} ;
2019-09-12 03:36:50 -07:00
2019-09-25 16:20:02 -07:00
use byteorder ::{ LittleEndian , ReadBytesExt , WriteBytesExt } ;
use chrono ::{ DateTime , TimeZone , Utc } ;
use zebra_chain ::serialization ::{
2021-04-05 16:49:42 -07:00
ReadZcashExt , SerializationError , TrustedPreallocate , WriteZcashExt , ZcashDeserialize ,
ZcashSerialize ,
2019-09-25 16:20:02 -07:00
} ;
2021-04-05 16:49:42 -07:00
use crate ::protocol ::{ external ::MAX_PROTOCOL_MESSAGE_LEN , types ::PeerServices } ;
2019-09-12 03:36:50 -07:00
2021-03-25 01:34:52 -07:00
use PeerAddrState ::* ;
2021-04-07 10:13:52 -07:00
#[ cfg(any(test, feature = " proptest-impl " )) ]
use proptest_derive ::Arbitrary ;
#[ cfg(any(test, feature = " proptest-impl " )) ]
mod arbitrary ;
2021-04-06 19:32:27 -07:00
#[ cfg(test) ]
mod tests ;
2021-02-17 17:18:32 -08:00
/// Peer connection state, based on our interactions with the peer.
///
/// Zebra also tracks how recently a peer has sent us messages, and derives peer
/// liveness based on the current time. This derived state is tracked using
/// [`AddressBook::maybe_connected_peers`] and
/// [`AddressBook::reconnection_peers`].
#[ derive(Copy, Clone, Debug, Eq, PartialEq) ]
2021-04-07 10:13:52 -07:00
#[ cfg_attr(any(test, feature = " proptest-impl " ), derive(Arbitrary)) ]
2021-02-17 17:18:32 -08:00
pub enum PeerAddrState {
/// The peer has sent us a valid message.
///
/// Peers remain in this state, even if they stop responding to requests.
/// (Peer liveness is derived from the `last_seen` timestamp, and the current
/// time.)
Responded ,
/// The peer's address has just been fetched from a DNS seeder, or via peer
/// gossip, but we haven't attempted to connect to it yet.
2021-05-06 17:50:04 -07:00
NeverAttemptedGossiped ,
/// The peer's address has just been received as part of a `Version` message,
/// so we might already be connected to this peer.
///
/// Alternate addresses are attempted after gossiped addresses.
NeverAttemptedAlternate ,
2021-02-17 17:18:32 -08:00
/// The peer's TCP connection failed, or the peer sent us an unexpected
/// Zcash protocol message, so we failed the connection.
Failed ,
/// We just started a connection attempt to this peer.
AttemptPending ,
}
2021-05-06 17:50:04 -07:00
// non-test code should explicitly specify the peer address state
#[ cfg(test) ]
2021-02-17 17:18:32 -08:00
impl Default for PeerAddrState {
fn default ( ) -> Self {
2021-05-06 17:50:04 -07:00
NeverAttemptedGossiped
2021-02-17 17:18:32 -08:00
}
}
impl Ord for PeerAddrState {
/// `PeerAddrState`s are sorted in approximate reconnection attempt
/// order, ignoring liveness.
///
/// See [`CandidateSet`] and [`MetaAddr::cmp`] for more details.
fn cmp ( & self , other : & Self ) -> Ordering {
2021-05-06 17:50:04 -07:00
use Ordering ::* ;
2021-02-17 17:18:32 -08:00
match ( self , other ) {
( Responded , Responded )
| ( Failed , Failed )
2021-05-06 17:50:04 -07:00
| ( NeverAttemptedGossiped , NeverAttemptedGossiped )
| ( NeverAttemptedAlternate , NeverAttemptedAlternate )
| ( AttemptPending , AttemptPending ) = > Equal ,
2021-02-17 17:18:32 -08:00
// We reconnect to `Responded` peers that have stopped sending messages,
// then `NeverAttempted` peers, then `Failed` peers
2021-05-06 17:50:04 -07:00
( Responded , _ ) = > Less ,
( _ , Responded ) = > Greater ,
( NeverAttemptedGossiped , _ ) = > Less ,
( _ , NeverAttemptedGossiped ) = > Greater ,
( NeverAttemptedAlternate , _ ) = > Less ,
( _ , NeverAttemptedAlternate ) = > Greater ,
( Failed , _ ) = > Less ,
( _ , Failed ) = > Greater ,
2021-02-17 17:18:32 -08:00
// AttemptPending is covered by the other cases
}
}
}
impl PartialOrd for PeerAddrState {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
Some ( self . cmp ( other ) )
}
}
2019-09-12 03:36:50 -07:00
/// An address with metadata on its advertised services and last-seen time.
2019-09-19 09:38:02 -07:00
///
2019-09-12 03:36:50 -07:00
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address)
2021-05-25 22:08:08 -07:00
#[ derive(Copy, Clone, Debug) ]
2019-09-12 03:36:50 -07:00
pub struct MetaAddr {
/// The peer's address.
2019-09-14 08:56:43 -07:00
pub addr : SocketAddr ,
2021-02-17 17:18:32 -08:00
2019-09-14 08:56:43 -07:00
/// The services advertised by the peer.
2021-02-17 17:18:32 -08:00
///
/// The exact meaning depends on `last_connection_state`:
/// - `Responded`: the services advertised by this peer, the last time we
/// performed a handshake with it
/// - `NeverAttempted`: the unverified services provided by the remote peer
/// that sent us this address
/// - `Failed` or `AttemptPending`: unverified services via another peer,
/// or services advertised in a previous handshake
///
/// ## Security
///
/// `services` from `NeverAttempted` peers may be invalid due to outdated
/// records, older peer versions, or buggy or malicious peers.
2019-09-30 11:40:44 -07:00
pub services : PeerServices ,
2021-02-17 17:18:32 -08:00
2021-03-25 01:36:06 -07:00
/// The last time we interacted with this peer.
///
/// See `get_last_seen` for details.
last_seen : DateTime < Utc > ,
/// The outcome of our most recent communication attempt with this peer.
pub last_connection_state : PeerAddrState ,
}
impl MetaAddr {
2021-05-20 01:22:45 -07:00
/// Create a new gossiped [`MetaAddr`], based on the deserialized fields from
2021-05-20 18:40:14 -07:00
/// a gossiped peer [`Addr`][crate::protocol::external::Message::Addr] message.
2021-05-20 01:22:45 -07:00
pub fn new_gossiped_meta_addr (
addr : SocketAddr ,
untrusted_services : PeerServices ,
untrusted_last_seen : DateTime < Utc > ,
2021-03-25 01:36:06 -07:00
) -> MetaAddr {
MetaAddr {
2021-05-20 01:22:45 -07:00
addr ,
services : untrusted_services ,
last_seen : untrusted_last_seen ,
2021-03-25 01:36:06 -07:00
// the state is Zebra-specific, it isn't part of the Zcash network protocol
2021-05-06 17:50:04 -07:00
last_connection_state : NeverAttemptedGossiped ,
2021-03-25 01:36:06 -07:00
}
}
/// Create a new `MetaAddr` for a peer that has just `Responded`.
2021-05-06 17:50:04 -07:00
///
/// # Security
///
2021-05-13 22:48:04 -07:00
/// This address must be the remote address from an outbound connection,
/// and the services must be the services from that peer's handshake.
///
2021-05-06 17:50:04 -07:00
/// Otherwise:
/// - malicious peers could interfere with other peers' `AddressBook` state,
/// or
/// - Zebra could advertise unreachable addresses to its own peers.
2021-03-25 01:36:06 -07:00
pub fn new_responded ( addr : & SocketAddr , services : & PeerServices ) -> MetaAddr {
MetaAddr {
addr : * addr ,
services : * services ,
last_seen : Utc ::now ( ) ,
last_connection_state : Responded ,
}
}
2021-03-25 03:14:52 -07:00
/// Create a new `MetaAddr` for a peer that we want to reconnect to.
pub fn new_reconnect ( addr : & SocketAddr , services : & PeerServices ) -> MetaAddr {
MetaAddr {
addr : * addr ,
services : * services ,
last_seen : Utc ::now ( ) ,
last_connection_state : AttemptPending ,
}
}
2021-05-06 17:50:04 -07:00
/// Create a new `MetaAddr` for a peer's alternate address, received via a
/// `Version` message.
pub fn new_alternate ( addr : & SocketAddr , services : & PeerServices ) -> MetaAddr {
MetaAddr {
addr : * addr ,
services : * services ,
last_seen : Utc ::now ( ) ,
last_connection_state : NeverAttemptedAlternate ,
}
}
2021-05-06 18:08:06 -07:00
/// Create a new `MetaAddr` for our own listener address.
pub fn new_local_listener ( addr : & SocketAddr ) -> MetaAddr {
MetaAddr {
addr : * addr ,
// TODO: create a "local services" constant
services : PeerServices ::NODE_NETWORK ,
last_seen : Utc ::now ( ) ,
last_connection_state : Responded ,
}
}
2021-03-25 03:14:52 -07:00
/// Create a new `MetaAddr` for a peer that has just had an error.
pub fn new_errored ( addr : & SocketAddr , services : & PeerServices ) -> MetaAddr {
MetaAddr {
addr : * addr ,
services : * services ,
last_seen : Utc ::now ( ) ,
last_connection_state : Failed ,
}
}
/// Create a new `MetaAddr` for a peer that has just shut down.
pub fn new_shutdown ( addr : & SocketAddr , services : & PeerServices ) -> MetaAddr {
// TODO: if the peer shut down in the Responded state, preserve that
// state. All other states should be treated as (timeout) errors.
MetaAddr ::new_errored ( addr , services )
}
2021-02-17 17:18:32 -08:00
/// The last time we interacted with this peer.
///
/// The exact meaning depends on `last_connection_state`:
/// - `Responded`: the last time we processed a message from this peer
/// - `NeverAttempted`: the unverified time provided by the remote peer
/// that sent us this address
/// - `Failed`: the last time we marked the peer as failed
/// - `AttemptPending`: the last time we queued the peer for a reconnection
/// attempt
///
/// ## Security
///
/// `last_seen` times from `NeverAttempted` peers may be invalid due to
/// clock skew, or buggy or malicious peers.
2021-03-25 01:36:06 -07:00
pub fn get_last_seen ( & self ) -> DateTime < Utc > {
self . last_seen
}
2021-02-17 17:18:32 -08:00
2021-05-13 22:48:04 -07:00
/// Is this address a directly connected client?
pub fn is_direct_client ( & self ) -> bool {
match self . last_connection_state {
Responded = > ! self . services . contains ( PeerServices ::NODE_NETWORK ) ,
NeverAttemptedGossiped | NeverAttemptedAlternate | Failed | AttemptPending = > false ,
}
}
2021-05-06 17:50:04 -07:00
/// Is this address valid for outbound connections?
pub fn is_valid_for_outbound ( & self ) -> bool {
self . services . contains ( PeerServices ::NODE_NETWORK )
& & ! self . addr . ip ( ) . is_unspecified ( )
& & self . addr . port ( ) ! = 0
}
2021-03-25 00:47:25 -07:00
/// Return a sanitized version of this `MetaAddr`, for sending to a remote peer.
pub fn sanitize ( & self ) -> MetaAddr {
2019-11-13 14:03:12 -08:00
let interval = crate ::constants ::TIMESTAMP_TRUNCATION_SECONDS ;
2021-03-25 01:36:06 -07:00
let ts = self . get_last_seen ( ) . timestamp ( ) ;
2021-03-25 00:47:25 -07:00
let last_seen = Utc . timestamp ( ts - ts . rem_euclid ( interval ) , 0 ) ;
MetaAddr {
addr : self . addr ,
2021-05-06 18:08:06 -07:00
// services are sanitized during parsing, or set to a fixed valued by
// new_local_listener, so we don't need to sanitize here
2021-03-25 00:47:25 -07:00
services : self . services ,
last_seen ,
// the state isn't sent to the remote peer, but sanitize it anyway
2021-05-06 17:50:04 -07:00
last_connection_state : NeverAttemptedGossiped ,
2021-03-25 00:47:25 -07:00
}
2019-11-13 14:03:12 -08:00
}
}
2019-10-17 21:19:23 -07:00
impl Ord for MetaAddr {
2021-02-17 17:18:32 -08:00
/// `MetaAddr`s are sorted in approximate reconnection attempt order, but
/// with `Responded` peers sorted first as a group.
///
/// This order should not be used for reconnection attempts: use
/// [`AddressBook::reconnection_peers`] instead.
///
/// See [`CandidateSet`] for more details.
2019-10-17 21:19:23 -07:00
fn cmp ( & self , other : & Self ) -> Ordering {
2021-02-17 17:18:32 -08:00
use std ::net ::IpAddr ::{ V4 , V6 } ;
2021-05-06 17:50:04 -07:00
use Ordering ::* ;
2021-02-17 17:18:32 -08:00
2021-03-25 01:36:06 -07:00
let oldest_first = self . get_last_seen ( ) . cmp ( & other . get_last_seen ( ) ) ;
2021-02-17 17:18:32 -08:00
let newest_first = oldest_first . reverse ( ) ;
let connection_state = self . last_connection_state . cmp ( & other . last_connection_state ) ;
let reconnection_time = match self . last_connection_state {
Responded = > oldest_first ,
2021-05-06 17:50:04 -07:00
NeverAttemptedGossiped = > newest_first ,
NeverAttemptedAlternate = > newest_first ,
2021-02-17 17:18:32 -08:00
Failed = > oldest_first ,
AttemptPending = > oldest_first ,
} ;
let ip_numeric = match ( self . addr . ip ( ) , other . addr . ip ( ) ) {
( V4 ( a ) , V4 ( b ) ) = > a . octets ( ) . cmp ( & b . octets ( ) ) ,
( V6 ( a ) , V6 ( b ) ) = > a . octets ( ) . cmp ( & b . octets ( ) ) ,
2021-05-06 17:50:04 -07:00
( V4 ( _ ) , V6 ( _ ) ) = > Less ,
( V6 ( _ ) , V4 ( _ ) ) = > Greater ,
2021-02-17 17:18:32 -08:00
} ;
connection_state
. then ( reconnection_time )
2019-10-17 21:19:23 -07:00
// The remainder is meaningless as an ordering, but required so that we
// have a total order on `MetaAddr` values: self and other must compare
2021-05-06 17:50:04 -07:00
// as Equal iff they are equal.
2021-02-17 17:18:32 -08:00
. then ( ip_numeric )
2019-10-17 21:19:23 -07:00
. then ( self . addr . port ( ) . cmp ( & other . addr . port ( ) ) )
. then ( self . services . bits ( ) . cmp ( & other . services . bits ( ) ) )
}
}
impl PartialOrd for MetaAddr {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
Some ( self . cmp ( other ) )
}
}
2021-05-25 22:08:08 -07:00
impl PartialEq for MetaAddr {
fn eq ( & self , other : & Self ) -> bool {
self . cmp ( other ) = = Ordering ::Equal
}
}
impl Eq for MetaAddr { }
2019-09-25 16:20:02 -07:00
impl ZcashSerialize for MetaAddr {
2020-02-05 14:32:10 -08:00
fn zcash_serialize < W : Write > ( & self , mut writer : W ) -> Result < ( ) , std ::io ::Error > {
2021-05-17 13:53:10 -07:00
writer . write_u32 ::< LittleEndian > (
self . get_last_seen ( )
. timestamp ( )
. try_into ( )
2021-05-25 20:19:17 -07:00
. map_err ( | e | std ::io ::Error ::new ( std ::io ::ErrorKind ::InvalidData , e ) ) ? ,
2021-05-17 13:53:10 -07:00
) ? ;
2019-09-30 11:58:32 -07:00
writer . write_u64 ::< LittleEndian > ( self . services . bits ( ) ) ? ;
2019-09-25 16:20:02 -07:00
writer . write_socket_addr ( self . addr ) ? ;
Ok ( ( ) )
}
}
impl ZcashDeserialize for MetaAddr {
fn zcash_deserialize < R : Read > ( mut reader : R ) -> Result < Self , SerializationError > {
2021-05-17 13:53:10 -07:00
// This can't panic, because all u32 values are valid `Utc.timestamp`s
2021-05-20 01:22:45 -07:00
let untrusted_last_seen = Utc . timestamp ( reader . read_u32 ::< LittleEndian > ( ) ? . into ( ) , 0 ) ;
let untrusted_services =
PeerServices ::from_bits_truncate ( reader . read_u64 ::< LittleEndian > ( ) ? ) ;
2021-03-25 01:36:06 -07:00
let addr = reader . read_socket_addr ( ) ? ;
2021-05-20 01:22:45 -07:00
Ok ( MetaAddr ::new_gossiped_meta_addr (
addr ,
untrusted_services ,
untrusted_last_seen ,
) )
2019-09-25 16:20:02 -07:00
}
}
2021-04-06 19:32:27 -07:00
2021-04-05 16:49:42 -07:00
/// A serialized meta addr has a 4 byte time, 8 byte services, 16 byte IP addr, and 2 byte port
const META_ADDR_SIZE : usize = 4 + 8 + 16 + 2 ;
2021-04-06 19:32:27 -07:00
2021-04-05 16:49:42 -07:00
impl TrustedPreallocate for MetaAddr {
fn max_allocation ( ) -> u64 {
// Since a maximal serialized Vec<MetAddr> uses at least three bytes for its length (2MB messages / 30B MetaAddr implies the maximal length is much greater than 253)
// the max allocation can never exceed (MAX_PROTOCOL_MESSAGE_LEN - 3) / META_ADDR_SIZE
( ( MAX_PROTOCOL_MESSAGE_LEN - 3 ) / META_ADDR_SIZE ) as u64
}
}