peer_addr.rs (6596B)
1 use std::{ 2 io::ErrorKind, 3 net::{IpAddr, SocketAddr}, 4 time::Duration, 5 }; 6 7 use anyhow::{Context, Result}; 8 9 use crate::domain::{ValidationError, error_has_validation}; 10 11 use super::SyncError; 12 13 pub(super) fn next_reconnect_delay(current: Duration, max_delay: Duration) -> Duration { 14 (current * 2).min(max_delay) 15 } 16 17 pub(super) fn reachable_advertised_addr( 18 advertised_addr: SocketAddr, 19 remote_addr: SocketAddr, 20 ) -> SocketAddr { 21 let mut reachable_addr = advertised_addr; 22 if reachable_addr.ip().is_unspecified() { 23 reachable_addr.set_ip(remote_addr.ip()); 24 } 25 reachable_addr 26 } 27 28 pub(super) fn normalize_advertised_peer(address: &str, remote_addr: SocketAddr) -> Result<String> { 29 let advertised_addr = address 30 .parse::<SocketAddr>() 31 .with_context(|| format!("invalid announced peer address {address}"))?; 32 Ok(reachable_advertised_addr(advertised_addr, remote_addr).to_string()) 33 } 34 35 pub(super) fn peer_list_address_is_discoverable( 36 address: &str, 37 remote_addr: SocketAddr, 38 ) -> Result<bool> { 39 let candidate = address 40 .parse::<SocketAddr>() 41 .with_context(|| format!("invalid peer-list address {address}"))?; 42 Ok(socket_addr_is_discoverable(candidate, remote_addr)) 43 } 44 45 pub(super) fn advertised_peer_is_discoverable( 46 address: &str, 47 remote_addr: SocketAddr, 48 ) -> Result<bool> { 49 let candidate = address 50 .parse::<SocketAddr>() 51 .with_context(|| format!("invalid announced peer address {address}"))?; 52 Ok(socket_addr_is_discoverable(candidate, remote_addr)) 53 } 54 55 fn socket_addr_is_discoverable(candidate: SocketAddr, remote_addr: SocketAddr) -> bool { 56 if candidate.ip().is_loopback() { 57 return remote_addr.ip().is_loopback(); 58 } 59 ip_is_publicly_discoverable(candidate.ip()) 60 } 61 62 fn ip_is_publicly_discoverable(ip: IpAddr) -> bool { 63 match ip { 64 IpAddr::V4(ip) => { 65 let [a, b, c, d] = ip.octets(); 66 !(a == 0 67 || a == 10 68 || a == 127 69 || (a == 100 && (64..=127).contains(&b)) 70 || (a == 169 && b == 254) 71 || (a == 172 && (16..=31).contains(&b)) 72 || (a == 192 && b == 168) 73 || (a == 192 && b == 0 && c == 2) 74 || (a == 198 && b == 51 && c == 100) 75 || (a == 203 && b == 0 && c == 113) 76 || a >= 224 77 || [a, b, c, d] == [255, 255, 255, 255]) 78 } 79 IpAddr::V6(ip) => { 80 let segments = ip.segments(); 81 !(ip.is_unspecified() 82 || ip.is_loopback() 83 || (segments[0] & 0xfe00) == 0xfc00 84 || (segments[0] & 0xffc0) == 0xfe80 85 || (segments[0] & 0xff00) == 0xff00) 86 } 87 } 88 } 89 90 pub(super) fn is_self_peer_address_for( 91 address: &str, 92 listen_addr: SocketAddr, 93 advertised_addr: Option<SocketAddr>, 94 ) -> bool { 95 address.parse::<SocketAddr>().is_ok_and(|candidate| { 96 is_self_socket_addr(candidate, listen_addr) 97 || advertised_addr.is_some_and(|addr| is_self_socket_addr(candidate, addr)) 98 }) 99 } 100 101 fn is_self_socket_addr(candidate: SocketAddr, listen_addr: SocketAddr) -> bool { 102 if candidate == listen_addr { 103 return true; 104 } 105 if candidate.port() != listen_addr.port() { 106 return false; 107 } 108 109 let candidate_ip = candidate.ip(); 110 let listen_ip = listen_addr.ip(); 111 if listen_ip.is_unspecified() { 112 return candidate_ip.is_unspecified() || candidate_ip.is_loopback(); 113 } 114 if candidate_ip.is_unspecified() { 115 return listen_ip.is_loopback(); 116 } 117 false 118 } 119 120 pub(super) fn is_quiet_disconnect(error: &anyhow::Error) -> bool { 121 error.chain().any(|cause| { 122 cause.downcast_ref::<std::io::Error>().is_some_and(|error| { 123 matches!( 124 error.kind(), 125 ErrorKind::ConnectionReset 126 | ErrorKind::BrokenPipe 127 | ErrorKind::UnexpectedEof 128 | ErrorKind::ConnectionAborted 129 ) 130 }) 131 }) 132 } 133 134 pub(super) fn is_possible_fork_error(error: &anyhow::Error) -> bool { 135 error_has_validation(error, ValidationError::requests_fork_recovery) 136 || error.chain().any(|cause| { 137 matches!( 138 cause.downcast_ref::<SyncError>(), 139 Some(SyncError::BlockPageHasNoCommonAncestor) 140 ) 141 }) 142 } 143 144 pub(super) fn inbound_error_counts_as_misbehavior(error: &anyhow::Error) -> bool { 145 !is_possible_fork_error(error) 146 && !error_has_validation(error, |kind| kind.is_fork_relative() || kind.is_temporal()) 147 } 148 149 #[cfg(test)] 150 mod tests { 151 use anyhow::anyhow; 152 153 use crate::domain::ValidationError; 154 155 use super::super::SyncError; 156 157 use super::{inbound_error_counts_as_misbehavior, is_possible_fork_error}; 158 159 #[test] 160 fn future_and_unopened_rank_slot_errors_are_temporal_not_misbehavior() { 161 assert!(!inbound_error_counts_as_misbehavior(&anyhow::Error::new( 162 ValidationError::BlockTimestampTooFarInFuture 163 ))); 164 assert!(!inbound_error_counts_as_misbehavior(&anyhow::Error::new( 165 ValidationError::BlockBeforeFinalizerRankSlot { 166 rank: 1, 167 min_timestamp: 123, 168 } 169 ))); 170 assert!(inbound_error_counts_as_misbehavior(&anyhow!( 171 "block hash is invalid" 172 ))); 173 } 174 175 #[test] 176 fn missing_block_page_ancestor_triggers_fork_recovery_without_peer_penalty() { 177 let error = 178 anyhow::Error::new(SyncError::BlockPageHasNoCommonAncestor).context("block batch"); 179 180 assert!(is_possible_fork_error(&error)); 181 assert!(!inbound_error_counts_as_misbehavior(&error)); 182 } 183 184 #[test] 185 fn fork_scoped_gossip_errors_do_not_penalize_peers() { 186 for kind in [ 187 ValidationError::BurnBundleParentMismatch, 188 ValidationError::BurnAnchorOutsidePendingWindow, 189 ValidationError::MineAnchorNotOnChain, 190 ] { 191 let error = anyhow::Error::new(kind); 192 assert!(!inbound_error_counts_as_misbehavior(&error)); 193 assert!(!is_possible_fork_error(&error)); 194 } 195 196 assert!(inbound_error_counts_as_misbehavior(&anyhow!( 197 "burn bundle signature is invalid" 198 ))); 199 } 200 201 #[test] 202 fn matching_text_without_a_typed_error_is_not_trusted() { 203 let error = anyhow!("burn bundle parent hash is invalid"); 204 205 assert!(inbound_error_counts_as_misbehavior(&error)); 206 assert!(!is_possible_fork_error(&error)); 207 } 208 }