1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
//! Prioritizing outbound peer connections based on peer attributes.
use std::net::SocketAddr;
use zebra_chain::parameters::Network;
use crate::PeerSocketAddr;
use AttributePreference::*;
/// A level of preference for a peer attribute.
///
/// Invalid peer attributes are represented as errors.
///
/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
/// of this type.
///
/// The derived order depends on the order of the variants in the enum.
/// The variants are sorted in the order they are listed.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum AttributePreference {
/// This peer is more likely to be a valid Zcash network peer.
///
/// # Correctness
///
/// This variant must be declared as the first enum variant,
/// so that `Preferred` peers sort before `Acceptable` peers.
Preferred,
/// This peer is possibly a valid Zcash network peer.
Acceptable,
}
/// A level of preference for a peer.
///
/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
/// of this type.
///
/// The derived order depends on the order of the fields in the struct.
/// The first field determines the overall order, then later fields sort equal first field values.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct PeerPreference {
/// Is the peer using the canonical Zcash port for the configured [`Network`]?
canonical_port: AttributePreference,
}
impl AttributePreference {
/// Returns `Preferred` if `is_preferred` is true.
pub fn preferred_from(is_preferred: bool) -> Self {
if is_preferred {
Preferred
} else {
Acceptable
}
}
/// Returns `true` for `Preferred` attributes.
#[allow(dead_code)]
pub fn is_preferred(&self) -> bool {
match self {
Preferred => true,
Acceptable => false,
}
}
}
impl PeerPreference {
/// Return a preference for the peer at `peer_addr` on `network`.
///
/// Use the [`PeerPreference`] [`Ord`] implementation to sort preferred peers first.
pub fn new(
peer_addr: impl Into<PeerSocketAddr>,
network: impl Into<Option<Network>>,
) -> Result<PeerPreference, &'static str> {
let peer_addr = peer_addr.into();
address_is_valid_for_outbound_connections(peer_addr, network)?;
// This check only prefers the configured network, because
// address_is_valid_for_outbound_connections() rejects the port used by the other network.
let canonical_port =
AttributePreference::preferred_from([8232, 18232].contains(&peer_addr.port()));
Ok(PeerPreference { canonical_port })
}
}
/// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
/// connections?
///
/// Since the addresses in the address book are unique, this check can be
/// used to permanently reject entire [`MetaAddr`]s.
///
/// [`MetaAddr`]: crate::meta_addr::MetaAddr
pub fn address_is_valid_for_outbound_connections(
peer_addr: PeerSocketAddr,
network: impl Into<Option<Network>>,
) -> Result<(), &'static str> {
// TODO: make private IP addresses an error unless a debug config is set (#3117)
if peer_addr.ip().is_unspecified() {
return Err("invalid peer IP address: unspecified addresses can not be used for outbound connections");
}
// 0 is an invalid outbound port.
if peer_addr.port() == 0 {
return Err(
"invalid peer port: unspecified ports can not be used for outbound connections",
);
}
address_is_valid_for_inbound_listeners(*peer_addr, network)
}
/// Is the supplied [`SocketAddr`] valid for inbound listeners on `network`?
///
/// This is used to check Zebra's configured Zcash listener port.
pub fn address_is_valid_for_inbound_listeners(
listener_addr: SocketAddr,
network: impl Into<Option<Network>>,
) -> Result<(), &'static str> {
// TODO: make private IP addresses an error unless a debug config is set (#3117)
// Ignore ports used by potentially compatible nodes: misconfigured Zcash ports.
if let Some(network) = network.into() {
if listener_addr.port() == network.default_port() {
return Ok(());
}
if listener_addr.port() == 8232 {
return Err(
"invalid peer port: port is for Mainnet, but this node is configured for Testnet",
);
} else if listener_addr.port() == 18232 {
return Err(
"invalid peer port: port is for Testnet, but this node is configured for Mainnet",
);
}
}
// Ignore ports used by potentially compatible nodes: other coins and unsupported Zcash regtest.
if listener_addr.port() == 18344 {
return Err(
"invalid peer port: port is for Regtest, but Zebra does not support that network",
);
} else if [8033, 18033, 16125, 26125].contains(&listener_addr.port()) {
// These coins use the same network magic numbers as Zcash, so we have to reject them by port:
// - ZClassic: 8033/18033
// https://github.com/ZclassicCommunity/zclassic/blob/504362bbf72400f51acdba519e12707da44138c2/src/chainparams.cpp#L130
// - Flux/ZelCash: 16125/26125
return Err("invalid peer port: port is for a non-Zcash coin");
}
Ok(())
}