303 lines
8.8 KiB
Rust
303 lines
8.8 KiB
Rust
pub mod store;
|
|
pub use store::{Error, Store};
|
|
|
|
use std::cell::RefCell;
|
|
use std::ops::{Deref, DerefMut};
|
|
use std::{hash, net};
|
|
|
|
use cyphernet::addr::HostName;
|
|
use localtime::LocalTime;
|
|
use nonempty::NonEmpty;
|
|
|
|
use crate::collections::RandomMap;
|
|
use crate::node::{Address, Alias, Penalty, UserAgent};
|
|
use crate::prelude::Timestamp;
|
|
use crate::{node, profile};
|
|
|
|
/// A map with the ability to randomly select values.
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
#[serde(transparent)]
|
|
pub struct AddressBook<K: hash::Hash + Eq, V> {
|
|
inner: RandomMap<K, V>,
|
|
#[serde(skip)]
|
|
rng: RefCell<fastrand::Rng>,
|
|
}
|
|
|
|
impl<K: hash::Hash + Eq, V> AddressBook<K, V> {
|
|
/// Create a new address book.
|
|
pub fn new(rng: fastrand::Rng) -> Self {
|
|
Self {
|
|
inner: RandomMap::with_hasher(rng.clone().into()),
|
|
rng: RefCell::new(rng),
|
|
}
|
|
}
|
|
|
|
/// Pick a random value in the book.
|
|
pub fn sample(&self) -> Option<(&K, &V)> {
|
|
self.sample_with(|_, _| true)
|
|
}
|
|
|
|
/// Pick a random value in the book matching a predicate.
|
|
pub fn sample_with(&self, mut predicate: impl FnMut(&K, &V) -> bool) -> Option<(&K, &V)> {
|
|
if let Some(pairs) = NonEmpty::from_vec(
|
|
self.inner
|
|
.iter()
|
|
.filter(|(k, v)| predicate(*k, *v))
|
|
.collect(),
|
|
) {
|
|
let ix = self.rng.borrow_mut().usize(..pairs.len());
|
|
let pair = pairs[ix]; // Can't fail.
|
|
|
|
Some(pair)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Return a new address book with the given RNG.
|
|
pub fn with(self, rng: fastrand::Rng) -> Self {
|
|
Self {
|
|
inner: self.inner,
|
|
rng: RefCell::new(rng),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<K: hash::Hash + Eq + Ord + Copy, V> AddressBook<K, V> {
|
|
/// Return a shuffled iterator.
|
|
pub fn shuffled(&self) -> std::vec::IntoIter<(&K, &V)> {
|
|
let mut items = self.inner.iter().collect::<Vec<_>>();
|
|
items.sort_by_key(|(k, _)| *k);
|
|
self.rng.borrow_mut().shuffle(&mut items);
|
|
|
|
items.into_iter()
|
|
}
|
|
|
|
/// Turn this object into a shuffled iterator.
|
|
pub fn into_shuffled(self) -> impl Iterator<Item = (K, V)> {
|
|
let mut items = self.inner.into_iter().collect::<Vec<_>>();
|
|
items.sort_by_key(|(k, _)| *k);
|
|
self.rng.borrow_mut().shuffle(&mut items);
|
|
|
|
items.into_iter()
|
|
}
|
|
|
|
/// Cycle through the keys at random. The random cycle repeats ad-infintum.
|
|
pub fn cycle(&self) -> impl Iterator<Item = &K> {
|
|
self.shuffled().map(|(k, _)| k).cycle()
|
|
}
|
|
}
|
|
|
|
impl<K: hash::Hash + Eq, V> FromIterator<(K, V)> for AddressBook<K, V> {
|
|
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
|
let rng = profile::env::rng();
|
|
let mut inner = RandomMap::with_hasher(rng.clone().into());
|
|
|
|
for (k, v) in iter {
|
|
inner.insert(k, v);
|
|
}
|
|
Self {
|
|
inner,
|
|
rng: RefCell::new(rng),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<K: hash::Hash + Eq, V> Deref for AddressBook<K, V> {
|
|
type Target = RandomMap<K, V>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl<K: hash::Hash + Eq, V> DerefMut for AddressBook<K, V> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.inner
|
|
}
|
|
}
|
|
|
|
/// Node public data.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Node {
|
|
/// Protocol version.
|
|
pub version: u8,
|
|
/// Advertised alias.
|
|
pub alias: Alias,
|
|
/// Advertised features.
|
|
pub features: node::Features,
|
|
/// Advertised addresses
|
|
pub addrs: Vec<KnownAddress>,
|
|
/// Proof-of-work included in node announcement.
|
|
pub pow: u32,
|
|
/// When this data was published.
|
|
pub timestamp: Timestamp,
|
|
/// User agent string.
|
|
pub agent: UserAgent,
|
|
/// Node connection penalty.
|
|
pub penalty: Penalty,
|
|
/// Whether the node is banned.
|
|
pub banned: bool,
|
|
}
|
|
|
|
/// A known address.
|
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct KnownAddress {
|
|
/// Network address.
|
|
pub addr: Address,
|
|
/// Address of the peer who sent us this address.
|
|
pub source: Source,
|
|
/// Last time this address was used to successfully connect to a peer.
|
|
pub last_success: Option<LocalTime>,
|
|
/// Last time this address was tried.
|
|
pub last_attempt: Option<LocalTime>,
|
|
/// Whether this address has been banned.
|
|
pub banned: bool,
|
|
}
|
|
|
|
impl KnownAddress {
|
|
/// Create a new known address.
|
|
pub fn new(addr: Address, source: Source) -> Self {
|
|
Self {
|
|
addr,
|
|
source,
|
|
last_success: None,
|
|
last_attempt: None,
|
|
banned: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Address source. Specifies where an address originated from.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub enum Source {
|
|
/// An address that was shared by another peer.
|
|
Peer,
|
|
/// A bootstrap node address.
|
|
Bootstrap,
|
|
/// An address that came from some source external to the system, eg.
|
|
/// specified by the user or added directly to the address manager.
|
|
Imported,
|
|
}
|
|
|
|
impl std::fmt::Display for Source {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Peer => write!(f, "Peer"),
|
|
Self::Bootstrap => write!(f, "Bootstrap"),
|
|
Self::Imported => write!(f, "Imported"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Address type.
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum AddressType {
|
|
Ipv4 = 1,
|
|
Ipv6 = 2,
|
|
Dns = 3,
|
|
#[cfg(feature = "tor")]
|
|
Onion = 4,
|
|
}
|
|
|
|
impl From<AddressType> for u8 {
|
|
fn from(other: AddressType) -> Self {
|
|
other as u8
|
|
}
|
|
}
|
|
|
|
impl From<&Address> for AddressType {
|
|
fn from(a: &Address) -> Self {
|
|
match a.host {
|
|
HostName::Ip(net::IpAddr::V4(_)) => AddressType::Ipv4,
|
|
HostName::Ip(net::IpAddr::V6(_)) => AddressType::Ipv6,
|
|
HostName::Dns(_) => AddressType::Dns,
|
|
#[cfg(feature = "tor")]
|
|
HostName::Tor(_) => AddressType::Onion,
|
|
_ => todo!(), // FIXME(cloudhead): Maxim will remove `non-exhaustive`
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for AddressType {
|
|
type Error = u8;
|
|
|
|
fn try_from(other: u8) -> Result<Self, Self::Error> {
|
|
match other {
|
|
1 => Ok(AddressType::Ipv4),
|
|
2 => Ok(AddressType::Ipv6),
|
|
3 => Ok(AddressType::Dns),
|
|
#[cfg(feature = "tor")]
|
|
4 => Ok(AddressType::Onion),
|
|
_ => Err(other),
|
|
}
|
|
}
|
|
}
|
|
/// Check whether an IP address is globally routable.
|
|
pub fn is_routable(addr: &net::IpAddr) -> bool {
|
|
match addr {
|
|
net::IpAddr::V4(addr) => ipv4_is_routable(addr),
|
|
net::IpAddr::V6(addr) => ipv6_is_routable(addr),
|
|
}
|
|
}
|
|
|
|
/// Check whether an IP address is locally routable.
|
|
pub fn is_local(addr: &net::IpAddr) -> bool {
|
|
match addr {
|
|
net::IpAddr::V4(addr) => {
|
|
addr.is_private() || addr.is_loopback() || addr.is_link_local() || addr.is_unspecified()
|
|
}
|
|
net::IpAddr::V6(addr) => {
|
|
addr.is_loopback() || addr.is_unicast_link_local() || addr.is_unspecified()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check whether an IPv4 address is globally routable.
|
|
///
|
|
/// This implementation lacks many exceptions, and should be improved once
|
|
/// corresponding functions in [`std::net::Ipv4Addr`] are stabilized.
|
|
///
|
|
/// See
|
|
/// - <https://github.com/rust-lang/rust/issues/27709>
|
|
/// - <https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml>
|
|
fn ipv4_is_routable(addr: &net::Ipv4Addr) -> bool {
|
|
// https://datatracker.ietf.org/doc/html/rfc7723#section-4.1
|
|
if *addr == net::Ipv4Addr::new(192, 0, 0, 9) {
|
|
return true;
|
|
}
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc8155#section-8.1
|
|
if *addr == net::Ipv4Addr::new(192, 0, 0, 10) {
|
|
return true;
|
|
}
|
|
|
|
// https://datatracker.ietf.org/doc/html/rfc791#section-3.2
|
|
if addr.octets()[0] == 0 {
|
|
return false;
|
|
}
|
|
|
|
!addr.is_private()
|
|
&& !addr.is_loopback()
|
|
&& !addr.is_link_local()
|
|
&& !addr.is_broadcast()
|
|
&& !addr.is_documentation()
|
|
}
|
|
|
|
/// Check whether an IPv6 address is globally routable.
|
|
///
|
|
/// This implementation lacks many exceptions, and should be improved once
|
|
/// corresponding functions in [`std::net::Ipv6Addr`] are stabilized.
|
|
///
|
|
/// See
|
|
/// - <https://github.com/rust-lang/rust/issues/27709>
|
|
/// - <https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml>
|
|
fn ipv6_is_routable(addr: &net::Ipv6Addr) -> bool {
|
|
!addr.is_loopback() && !addr.is_unicast_link_local() && !addr.is_unspecified()
|
|
}
|