use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::hash::Hash;
use std::io;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicU32, AtomicUsize};
use std::sync::Arc;
use std::time::{Duration, Instant};
use ipnet::IpNet;
use parking_lot::RwLock;
use rand::rngs::StdRng;
use rand::SeedableRng;
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};
use crate::bounds::{Key, Value};
use crate::clock::{Clock, HlcClock, NodeId, Timestamp};
use crate::discovery::{Discovery, RandomProbe};
use crate::entry::{Entry, State};
use crate::replicated_map::{Config, MIN_BULK_SEND_RATE};
use crate::transport::{Transport, UdpTransport};
use crate::FingerprintTreeMap;
use gossip::auth;
use gossip::gen_ip::{host_net, net_of};
use gossip::replay;
use rbsr::RangeAggregate;
const BUFFER_SIZE: usize = 65507;
pub(crate) const MAX_MESSAGES_PER_DATAGRAM: usize = BUFFER_SIZE;
const PEER_EXPIRATION: Duration = Duration::from_secs(60);
const TOMBSTONE_ACK_RESEND_BYTE_BUDGET: usize = 8 * 1024;
const MAX_SENDTO_RETRIES: u32 = 4;
type PreInsertCallback<K, V> = Box<dyn Send + Sync + Fn(&K, &V)>;
pub(crate) fn version_hash<V: Serialize>(value: &V) -> u64 {
rsos::digest(value).0[0]
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct PeerCap(usize);
impl PeerCap {
pub(crate) fn new(max_peers: usize) -> Self {
PeerCap(max_peers)
}
pub(crate) fn admits(self, known: bool, current_len: usize) -> bool {
known || current_len < self.0
}
pub(crate) fn max(self) -> usize {
self.0
}
}
fn derive_local_net(nets: &[IpNet], listen_addr: IpAddr) -> IpNet {
net_of(nets, listen_addr).unwrap_or_else(|| {
warn!(
"listen address {listen_addr} is contained in none of the configured networks \
{nets:?}; cannot identify the local network โ treating only this node as local, so \
every peer is remote and reconciled on the throttled cross-network cadence. Declare \
the network containing {listen_addr} via Config::with_net or ReplicatedMap::add_net.",
);
host_net(listen_addr)
})
}
pub(crate) struct Replica<K, V> {
inner: Arc<Inner<K, V>>,
}
pub(crate) struct Inner<K, V> {
pub(crate) map: Arc<RwLock<FingerprintTreeMap<K, Entry<Timestamp, V>>>>,
pub(crate) projection: Arc<RwLock<FingerprintTreeMap<K, State<V>>>>,
port: u16,
transport: Arc<dyn Transport<Addr = SocketAddr>>,
nets: Arc<RwLock<Vec<IpNet>>>,
local_net: Arc<RwLock<IpNet>>,
listen_addr: IpAddr,
remote_interval: Arc<AtomicU32>,
remote_fanout: Arc<AtomicUsize>,
reconcile_interval: Arc<RwLock<Duration>>,
bulk_send_rate: Option<usize>,
bulk_in_flight: Arc<RwLock<HashSet<SocketAddr>>>,
bulk_dumps_in_flight: Arc<AtomicUsize>,
max_concurrent_bulk_dumps: usize,
round: Arc<AtomicU32>,
rng: Arc<RwLock<StdRng>>,
probe: Arc<dyn Discovery>,
pub(crate) peers: Arc<RwLock<HashMap<IpAddr, Instant>>>,
pub(crate) pre_insert: Arc<RwLock<PreInsertCallback<K, Entry<Timestamp, V>>>>,
authenticator: auth::Authenticator,
sender_counter: Arc<replay::SenderCounter>,
replay_filter: Arc<replay::ReplayFilter>,
pub(crate) members: Arc<RwLock<HashSet<IpAddr>>>,
pub(crate) tombstone_acks: Arc<RwLock<HashMap<K, HashMap<IpAddr, u64>>>>,
pub(crate) live_tombstones: Arc<RwLock<HashSet<K>>>,
clock: Arc<dyn Clock>,
pub(crate) node_id_is_random: bool,
max_peers: PeerCap,
}
impl<K, V> Clone for Replica<K, V> {
fn clone(&self) -> Self {
Replica {
inner: Arc::clone(&self.inner),
}
}
}
impl<K, V> std::ops::Deref for Replica<K, V> {
type Target = Inner<K, V>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) enum Message<K: Serialize, V: Serialize, P: Serialize> {
ComparisonItem(RangeAggregate<K>),
Update((K, V)),
Ack((K, u64)),
ValueComparisonItem(RangeAggregate<K>),
ValueUpdate((K, P)),
}
impl<K: Key + Hash, V: Value> Replica<K, V> {
pub async fn new(config: Config) -> io::Result<Self> {
let node_id_is_random = config.node_id.is_none();
let node_id = config
.node_id
.unwrap_or_else(|| NodeId::new(rand::random()));
let clock: Arc<dyn Clock> = Arc::new(HlcClock::new(node_id));
let transport = Self::bind_udp(&config).await?;
Ok(Self::build(config, transport, clock, node_id_is_random))
}
pub async fn new_with_clock(config: Config, clock: Arc<dyn Clock>) -> io::Result<Self> {
let transport = Self::bind_udp(&config).await?;
Ok(Self::build(config, transport, clock, false))
}
pub(crate) fn with_transport(
config: Config,
transport: Arc<dyn Transport<Addr = SocketAddr>>,
) -> Self {
let node_id_is_random = config.node_id.is_none();
let node_id = config
.node_id
.unwrap_or_else(|| NodeId::new(rand::random()));
let clock: Arc<dyn Clock> = Arc::new(HlcClock::new(node_id));
Self::build(config, transport, clock, node_id_is_random)
}
#[cfg(test)]
pub(crate) fn new_with_transport(
config: Config,
transport: Arc<dyn Transport<Addr = SocketAddr>>,
clock: Arc<dyn Clock>,
) -> Self {
Self::build(config, transport, clock, false)
}
async fn bind_udp(config: &Config) -> io::Result<Arc<dyn Transport<Addr = SocketAddr>>> {
let transport = UdpTransport::bind(
SocketAddr::new(config.listen_addr, config.port),
config.recv_buffer_size,
config.send_buffer_size,
)
.await?;
info!("Listening on: {}", transport.local_addr()?);
Ok(Arc::new(transport))
}
}
impl<K: Key + Hash, V: Value> Replica<K, V> {
fn build(
config: Config,
transport: Arc<dyn Transport<Addr = SocketAddr>>,
clock: Arc<dyn Clock>,
node_id_is_random: bool,
) -> Self {
config.check_key_or_insecure_opt_in();
let authenticator = auth::Authenticator::new(config.cluster_key, config.encrypt);
match &authenticator {
#[cfg(feature = "encryption")]
auth::Authenticator::Encrypted(_) => {
debug!("per-datagram authenticated encryption (XChaCha20-Poly1305) ENABLED");
}
auth::Authenticator::Enabled(_) => {
debug!("per-datagram MAC authentication ENABLED");
}
auth::Authenticator::Disabled => {
warn!(
"SECURITY: running with Config::with_insecure_no_key() โ UDP reconciliation \
is UNAUTHENTICATED. Any host that can send UDP to this port can forge \
updates and poison the cluster via last-write-wins, and any host inside the \
configured nets will eventually receive the ENTIRE DATASET via paced diff \
dumps once RandomProbe discovers it. Set Config::with_cluster_key on every \
node, or restrict the network to a trusted underlay. See REVIEW.md F3."
);
}
}
let authenticator_enabled = !matches!(authenticator, auth::Authenticator::Disabled);
let bulk_send_rate = config.bulk_send_rate.map(|rate| {
if rate > 0 && rate < MIN_BULK_SEND_RATE {
warn!(
"bulk_send_rate {rate} B/s is below the {MIN_BULK_SEND_RATE} B/s floor and \
would wedge a peer's bulk dump for an effectively unbounded sleep; clamping \
up to the floor. See #331."
);
MIN_BULK_SEND_RATE
} else {
rate
}
});
let map = FingerprintTreeMap::<K, Entry<Timestamp, V>>::new();
let projection = FingerprintTreeMap::<K, State<V>>::new();
let mut nets: Vec<IpNet> = config.nets.iter().flatten().copied().collect();
if nets.is_empty() {
nets.push("127.0.0.1/8".parse().unwrap());
}
let local_net = derive_local_net(&nets, config.listen_addr);
let nets = Arc::new(RwLock::new(nets));
let rng = Arc::new(RwLock::new(StdRng::from_entropy()));
let probe: Arc<dyn Discovery> =
Arc::new(RandomProbe::new(Arc::clone(&nets), Arc::clone(&rng)));
Replica {
inner: Arc::new(Inner {
map: Arc::new(RwLock::new(map)),
projection: Arc::new(RwLock::new(projection)),
port: config.port,
transport,
nets,
local_net: Arc::new(RwLock::new(local_net)),
listen_addr: config.listen_addr,
remote_interval: Arc::new(AtomicU32::new(config.remote_interval)),
remote_fanout: Arc::new(AtomicUsize::new(config.remote_fanout)),
reconcile_interval: Arc::new(RwLock::new(config.reconcile_interval)),
bulk_send_rate,
bulk_in_flight: Arc::new(RwLock::new(HashSet::new())),
bulk_dumps_in_flight: Arc::new(AtomicUsize::new(0)),
max_concurrent_bulk_dumps: config.max_concurrent_bulk_dumps,
round: Arc::new(AtomicU32::new(0)),
rng,
probe,
peers: Arc::new(RwLock::new(HashMap::new())),
pre_insert: Arc::new(RwLock::new(Box::new(|_, _| {}))),
authenticator,
sender_counter: Arc::new(replay::SenderCounter::new()),
replay_filter: Arc::new(replay::ReplayFilter::new(
config.freshness_window,
authenticator_enabled,
)),
members: Arc::new(RwLock::new(HashSet::new())),
tombstone_acks: Arc::new(RwLock::new(HashMap::new())),
live_tombstones: Arc::new(RwLock::new(HashSet::new())),
clock,
node_id_is_random,
max_peers: PeerCap::new(config.max_peers),
}),
}
}
}
mod gc;
mod membership;
mod pacing;
mod read;
mod run;
mod write;
#[cfg(test)]
pub(crate) use pacing::send_messages_paced;
pub(crate) use pacing::{send_messages_to, send_to_retry, SendPorts};
#[cfg(test)]
mod tests;