Skip to main content

kinship_net/
lib.rs

1//! tokio UDP and TCP driver for kinship-core.
2//!
3//! [`Memberlist::start`] binds nothing itself: it takes a [`Transport`], usually a
4//! [`TokioTransport`] bound with [`TokioTransport::bind`], builds the sans-IO
5//! [`kinship_core::Node`] and spawns one actor task that owns it. The actor feeds the node every
6//! datagram, stream frame, timer expiry and command, and after each input drains what the node
7//! wants sent and what it has to report. Handles talk to the actor over a channel with oneshot
8//! replies, read membership from a snapshot the actor publishes after every change, and receive
9//! events through bounded per-subscription queues that never make the actor wait.
10//!
11//! The `kinship` crate wraps this as `Cluster`, with a builder for the configuration.
12
13mod actor;
14mod conn;
15mod events;
16pub mod mem;
17mod seed;
18mod transport;
19
20use std::fmt;
21use std::io;
22use std::net::SocketAddr;
23use std::sync::{Arc, Mutex, PoisonError};
24use std::time::Duration;
25
26use arc_swap::ArcSwap;
27use tokio::sync::{mpsc, oneshot};
28use tokio::task::{AbortHandle, JoinHandle};
29
30pub use events::{Event, Events};
31pub use kinship_core::{
32    CommandError, Config, ConfigError, Identity, Key, KeyError, KeyId, Limits, Member, Metrics,
33    Security, State, WIRE_VERSION,
34};
35pub use seed::{Seed, SeedError};
36pub use transport::{TokioTransport, Transport, default_advertise};
37
38use crate::actor::{Actor, Clock, KeyChange, Request, Shared, Snapshot};
39use crate::events::Hub;
40
41/// Everything a node needs that the transport does not decide.
42#[derive(Debug, Clone)]
43pub struct Settings {
44    /// The protocol configuration, with its security already chosen.
45    pub core: Config,
46    /// This node's name: 1 to 64 bytes of UTF-8, unique in the cluster.
47    pub name: String,
48    /// The address peers use to reach this node. `None` derives it from the transport's address
49    /// with [`default_advertise`].
50    pub advertise: Option<SocketAddr>,
51    /// Initial metadata, at most `max_meta_bytes`.
52    pub meta: Vec<u8>,
53    /// Joined on start, and every `rejoin_interval` each one that is not a live member is
54    /// push-pulled with again.
55    pub seeds: Vec<SocketAddr>,
56    /// Seeds that may be DNS names, joined and rejoined like `seeds`. Each name is looked up
57    /// again at every join and rejoin, and every address it has, A and AAAA records alike, is a
58    /// seed, so a name with several records, such as a Kubernetes headless Service, works as a
59    /// seed list.
60    pub seed_hosts: Vec<Seed>,
61    /// How long a join looks names up again, with backoff, while no seed resolves. A start whose
62    /// seeds stay unresolved this long fails with [`Error::Unresolved`]; once one resolves, the
63    /// others are looked up again at every rejoin.
64    pub resolve_timeout: Duration,
65    /// Inbound TCP connections served at once. A new one beyond this drops the oldest of the
66    /// source address with the most.
67    pub max_inbound_streams: usize,
68    /// Inbound TCP connections served at once from one source address. A new one beyond this
69    /// drops that address's oldest.
70    pub max_inbound_streams_per_ip: usize,
71    /// Bytes of unauthenticated inbound frames that connections from one source address may
72    /// hold at once, out of the twice `max_stream_frame` all inbound connections share. `None`
73    /// is one largest frame: `max_stream_frame` and its 4-byte length prefix.
74    pub max_inbound_bytes_per_ip: Option<usize>,
75    /// How long an inbound TCP connection has to send the length prefix and the first 8 header
76    /// bytes of its frame, or `tcp_timeout` if that is shorter.
77    pub tcp_header_timeout: Duration,
78    /// Events each subscription holds before the oldest are dropped.
79    pub event_buffer: usize,
80}
81
82impl Settings {
83    pub const DEFAULT_MAX_INBOUND_STREAMS: usize = 64;
84    pub const DEFAULT_MAX_INBOUND_STREAMS_PER_IP: usize = 16;
85    pub const DEFAULT_TCP_HEADER_TIMEOUT: Duration = Duration::from_secs(1);
86    pub const DEFAULT_EVENT_BUFFER: usize = 1024;
87    pub const DEFAULT_RESOLVE_TIMEOUT: Duration = Duration::from_secs(30);
88
89    /// Settings with no metadata, no seeds and the default limits.
90    pub fn new(core: Config, name: impl Into<String>) -> Self {
91        Self {
92            core,
93            name: name.into(),
94            advertise: None,
95            meta: Vec::new(),
96            seeds: Vec::new(),
97            seed_hosts: Vec::new(),
98            resolve_timeout: Self::DEFAULT_RESOLVE_TIMEOUT,
99            max_inbound_streams: Self::DEFAULT_MAX_INBOUND_STREAMS,
100            max_inbound_streams_per_ip: Self::DEFAULT_MAX_INBOUND_STREAMS_PER_IP,
101            max_inbound_bytes_per_ip: None,
102            tcp_header_timeout: Self::DEFAULT_TCP_HEADER_TIMEOUT,
103            event_buffer: Self::DEFAULT_EVENT_BUFFER,
104        }
105    }
106
107    /// Bytes of unauthenticated inbound frames one source address may hold:
108    /// `max_inbound_bytes_per_ip`, or one largest frame.
109    pub fn inbound_bytes_per_ip(&self) -> usize {
110        self.max_inbound_bytes_per_ip.unwrap_or_else(|| {
111            self.core
112                .limits
113                .max_stream_frame
114                .saturating_add(FRAME_PREFIX_LEN)
115        })
116    }
117
118    /// Checks the fields the core does not; [`Memberlist::start`] calls it too.
119    pub fn validate(&self) -> Result<(), ConfigError> {
120        let fail = |field, reason| Err(ConfigError { field, reason });
121        if self.max_inbound_streams == 0 {
122            return fail("max_inbound_streams", "must be at least 1");
123        }
124        if self.max_inbound_streams_per_ip == 0 {
125            return fail("max_inbound_streams_per_ip", "must be at least 1");
126        }
127        let frame = self.core.limits.max_stream_frame;
128        if self.inbound_bytes_per_ip() < frame.saturating_add(FRAME_PREFIX_LEN) {
129            return fail(
130                "max_inbound_bytes_per_ip",
131                "must hold one max_stream_frame frame and its 4-byte length prefix",
132            );
133        }
134        if self.tcp_header_timeout.is_zero() {
135            return fail("tcp_header_timeout", "must be positive");
136        }
137        if self.event_buffer == 0 {
138            return fail("event_buffer", "must be at least 1");
139        }
140        Ok(())
141    }
142}
143
144/// The `u32` length prefix in front of every stream frame.
145const FRAME_PREFIX_LEN: usize = 4;
146
147/// `seeds` and `seed_hosts` as one list.
148fn all_seeds(settings: &Settings) -> Vec<Seed> {
149    let addrs = settings.seeds.iter().copied().map(Seed::Addr);
150    addrs.chain(settings.seed_hosts.iter().cloned()).collect()
151}
152
153/// The epoch of this life of the node: milliseconds of the wall clock since the Unix epoch,
154/// which a process that restarts later reads higher. This is the one read of the wall clock,
155/// and it names the node rather than timing anything, so a jump cannot move a timer. A clock
156/// that reads below an earlier life of the same name is caught by the core, which moves past
157/// that life once it hears of it; one before 1970 reads as 1, since 0 means no epoch.
158#[allow(clippy::disallowed_types, clippy::disallowed_methods)]
159fn start_epoch() -> u64 {
160    let since = std::time::SystemTime::now()
161        .duration_since(std::time::UNIX_EPOCH)
162        .unwrap_or_default();
163    u64::try_from(since.as_millis()).unwrap_or(u64::MAX).max(1)
164}
165
166/// Counters from the protocol core and this node's Lifeguard local health score.
167#[derive(Debug, Clone, Default, PartialEq, Eq)]
168#[non_exhaustive]
169pub struct Stats {
170    pub metrics: Metrics,
171    /// Lifeguard's local health multiplier: 0 when healthy, up to `awareness_max`.
172    pub local_health: u32,
173}
174
175/// Why a call on a [`Memberlist`] failed.
176#[derive(Debug)]
177#[non_exhaustive]
178pub enum Error {
179    /// A configuration value is unusable; the error names the field.
180    Config(ConfigError),
181    /// Binding the sockets, or drawing the node's random seed, failed.
182    Io(io::Error),
183    /// No seed answered after `join_retries` attempts each.
184    JoinFailed,
185    /// No seed resolved to an address within `resolve_timeout`.
186    Unresolved,
187    /// Metadata is larger than `max_meta_bytes`.
188    MetaTooLarge,
189    /// A user message is larger than `max_user_message`, or a broadcast than
190    /// `max_user_broadcast`.
191    MessageTooLarge,
192    /// A user message for a name this node does not hold as a live member at that epoch: one
193    /// it does not know, one that died or left, a run that has since restarted, or itself.
194    NotAMember,
195    /// This node has left the cluster.
196    Left,
197    /// `leave()` ran out of time before the news finished spreading. The node has left anyway.
198    Timeout,
199    /// The node was closed, or its actor stopped.
200    Closed,
201    /// A keyring call on a node that runs without encryption.
202    NotEncrypted,
203    /// `use` of a key that is not installed.
204    KeyNotInstalled,
205    /// `remove` of the key the node encrypts with.
206    KeyInUse,
207    /// `remove` of the only installed key.
208    LastKey,
209}
210
211impl fmt::Display for Error {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        match self {
214            Self::Config(e) => e.fmt(f),
215            Self::Io(e) => write!(f, "I/O error: {e}"),
216            Self::JoinFailed => f.write_str("no seed answered"),
217            Self::Unresolved => f.write_str("no seed resolved to an address"),
218            Self::MetaTooLarge => f.write_str("metadata larger than max_meta_bytes"),
219            Self::MessageTooLarge => {
220                f.write_str("message larger than max_user_message or max_user_broadcast")
221            }
222            Self::NotAMember => f.write_str("not a live member at that epoch"),
223            Self::Left => f.write_str("this node has left the cluster"),
224            Self::Timeout => f.write_str("timed out"),
225            Self::Closed => f.write_str("the node is closed"),
226            Self::NotEncrypted => f.write_str("this node runs without encryption and has no keys"),
227            Self::KeyNotInstalled => f.write_str("key is not installed"),
228            Self::KeyInUse => f.write_str("key is the one in use"),
229            Self::LastKey => f.write_str("key is the last one installed"),
230        }
231    }
232}
233
234impl std::error::Error for Error {
235    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
236        match self {
237            Self::Config(e) => Some(e),
238            Self::Io(e) => Some(e),
239            _ => None,
240        }
241    }
242}
243
244impl From<ConfigError> for Error {
245    fn from(e: ConfigError) -> Self {
246        Self::Config(e)
247    }
248}
249
250impl From<io::Error> for Error {
251    fn from(e: io::Error) -> Self {
252        Self::Io(e)
253    }
254}
255
256impl From<CommandError> for Error {
257    fn from(e: CommandError) -> Self {
258        match e {
259            CommandError::MetaTooLarge => Self::MetaTooLarge,
260            CommandError::JoinFailed => Self::JoinFailed,
261            CommandError::Left => Self::Left,
262            CommandError::NotEncrypted => Self::NotEncrypted,
263            CommandError::KeyNotInstalled => Self::KeyNotInstalled,
264            CommandError::KeyInUse => Self::KeyInUse,
265            CommandError::LastKey => Self::LastKey,
266            CommandError::MessageTooLarge => Self::MessageTooLarge,
267            CommandError::NotAMember => Self::NotAMember,
268            _ => Self::Closed,
269        }
270    }
271}
272
273/// A new random key: 32 bytes from the operating system's secure random source.
274///
275/// Give the same key to every node. Print it with [`Key::to_base64`] to hand it out, as
276/// `python -m kinship keygen` does; never derive a key from a passphrase or a seeded RNG.
277pub fn generate_key() -> io::Result<Key> {
278    let mut bytes = [0u8; Key::LEN];
279    let drawn = getrandom::fill(&mut bytes).map_err(|e| io::Error::other(e.to_string()));
280    let key = drawn.map(|()| Key::from_bytes(bytes));
281    zeroize::Zeroize::zeroize(&mut bytes);
282    key
283}
284
285/// A node name for when none is configured: the host name plus 6 random hex characters, within
286/// the 64-byte limit. Set a stable name instead if the node should keep its identity across
287/// restarts.
288pub fn default_name() -> String {
289    let host = ["HOSTNAME", "COMPUTERNAME"]
290        .iter()
291        .find_map(|v| std::env::var(v).ok())
292        .or_else(|| std::fs::read_to_string("/etc/hostname").ok())
293        .map(|h| h.trim().to_owned())
294        .filter(|h| !h.is_empty())
295        .unwrap_or_else(|| "kinship".to_owned());
296    let mut end = host.len().min(57);
297    while !host.is_char_boundary(end) {
298        end -= 1;
299    }
300    let suffix = getrandom::u32().unwrap_or_default() & 0xff_ffff;
301    format!("{}-{suffix:06x}", &host[..end])
302}
303
304/// Requests queued for the actor before senders wait.
305const REQUEST_QUEUE: usize = 256;
306
307/// A running node: the handle to its actor.
308///
309/// Reads ([`members`](Self::members), [`member`](Self::member), [`local`](Self::local),
310/// [`stats`](Self::stats)) never wait on the actor. Calls that change the node go to the actor
311/// and wait for its answer. Dropping the handle stops the node as [`abort`](Self::abort) does.
312pub struct Memberlist {
313    requests: mpsc::Sender<Request>,
314    shared: Arc<Shared>,
315    local_addr: SocketAddr,
316    resolve_timeout: Duration,
317    task: Mutex<Option<JoinHandle<()>>>,
318    abort: AbortHandle,
319}
320
321impl Memberlist {
322    /// Starts a node on `transport` and joins `settings.seeds` and `settings.seed_hosts`,
323    /// skipping this node's own address.
324    ///
325    /// A startup join that reaches no seed only logs a warning: the node runs alone and retries
326    /// every `rejoin_interval`. Seed names that do not resolve are logged and looked up again,
327    /// but if no seed resolves within `resolve_timeout` the node stops and start fails with
328    /// [`Error::Unresolved`]. Must be called from within a tokio runtime, which runs the actor.
329    pub async fn start<T: Transport>(settings: Settings, transport: T) -> Result<Self, Error> {
330        let seeds = all_seeds(&settings);
331        let ml = Self::spawn(settings, transport)?;
332        if !seeds.is_empty() {
333            match ml.join_seeds(seeds).await {
334                Ok(n) => tracing::debug!(seeds = n, "joined"),
335                Err(Error::Closed) => return Err(Error::Closed),
336                Err(Error::Unresolved) => {
337                    tracing::error!("no seed resolved; stopping");
338                    ml.close().await;
339                    return Err(Error::Unresolved);
340                }
341                Err(e) => {
342                    tracing::warn!(error = %e, "startup join failed; retrying in the background")
343                }
344            }
345        }
346        Ok(ml)
347    }
348
349    fn spawn<T: Transport>(settings: Settings, transport: T) -> Result<Self, Error> {
350        settings.validate()?;
351        let seeds = all_seeds(&settings);
352        let resolve_timeout = settings.resolve_timeout;
353        let max_inbound_bytes_per_ip = settings.inbound_bytes_per_ip();
354        let local_addr = transport.local_addr();
355        let advertise = settings
356            .advertise
357            .unwrap_or_else(|| default_advertise(local_addr));
358        let me = kinship_core::Identity::new(settings.name, advertise)?
359            .with_meta(settings.meta)
360            .with_epoch(start_epoch());
361        // The core draws no randomness of its own: its protocol choices come from this seed and
362        // the random bytes of its nonces from ChaCha20 under this key, both from the OS.
363        let os = |e: getrandom::Error| Error::Io(io::Error::other(e.to_string()));
364        let seed = getrandom::u64().map_err(os)?;
365        let mut nonce_key = [0u8; 32];
366        getrandom::fill(&mut nonce_key).map_err(os)?;
367        let clock = Clock::new();
368        let limits = conn::Limits {
369            tcp_timeout: settings.core.tcp_timeout,
370            max_stream_frame: settings.core.limits.max_stream_frame,
371            header_timeout: settings.tcp_header_timeout,
372        };
373        let rejoin_interval = settings.core.rejoin_interval;
374        let node = kinship_core::Node::new(settings.core, me, clock.now(), seed, &nonce_key);
375        zeroize::Zeroize::zeroize(&mut nonce_key);
376        let node = node?;
377        tracing::info!(name = node.local().name, addr = %advertise, bind = %local_addr, "node started");
378        let shared = Arc::new(Shared {
379            snapshot: ArcSwap::from_pointee(Snapshot::of(&node)),
380            key_ids: ArcSwap::from_pointee(node.key_ids()),
381            stats: Mutex::default(),
382            hub: Hub::new(settings.event_buffer),
383        });
384        let (requests, rx) = mpsc::channel(REQUEST_QUEUE);
385        let opts = actor::Options {
386            limits,
387            max_inbound_streams: settings.max_inbound_streams,
388            max_inbound_streams_per_ip: settings.max_inbound_streams_per_ip,
389            max_inbound_bytes_per_ip,
390            seeds,
391            rejoin_interval,
392        };
393        let actor = Actor::new(
394            node,
395            clock,
396            Arc::new(transport),
397            Arc::clone(&shared),
398            rx,
399            opts,
400        );
401        let task = tokio::spawn(actor.run());
402        Ok(Self {
403            requests,
404            shared,
405            local_addr,
406            resolve_timeout,
407            abort: task.abort_handle(),
408            task: Mutex::new(Some(task)),
409        })
410    }
411
412    /// The address the transport is bound to; with port 0, the port the OS picked.
413    pub fn local_addr(&self) -> SocketAddr {
414        self.local_addr
415    }
416
417    /// This node as the cluster sees it.
418    pub fn local(&self) -> Member {
419        self.shared.snapshot.load().local().clone()
420    }
421
422    /// Alive and suspect members, this node included unless it has left.
423    pub fn members(&self) -> Vec<Member> {
424        let snapshot = self.shared.snapshot.load();
425        snapshot
426            .all
427            .iter()
428            .filter(|m| m.state.is_live())
429            .cloned()
430            .collect()
431    }
432
433    /// One member by name, including dead and left tombstones.
434    pub fn member(&self, name: &str) -> Option<Member> {
435        let snapshot = self.shared.snapshot.load();
436        snapshot.all.iter().find(|m| m.name == name).cloned()
437    }
438
439    /// A new subscription to events, starting now. See [`Events`].
440    pub fn events(&self) -> Events {
441        self.shared.hub.subscribe()
442    }
443
444    /// Protocol counters and the local health score.
445    pub fn stats(&self) -> Stats {
446        self.shared.stats().clone()
447    }
448
449    /// Changes this node's keys at runtime. See [`Keyring`].
450    pub fn keyring(&self) -> Keyring {
451        Keyring {
452            requests: self.requests.clone(),
453            shared: Arc::clone(&self.shared),
454        }
455    }
456
457    /// Push-pulls with every seed, retrying failed ones with backoff until one answers, and
458    /// returns how many answered. This node's own address is skipped.
459    pub async fn join(&self, seeds: impl IntoIterator<Item = SocketAddr>) -> Result<usize, Error> {
460        self.join_seeds(seeds.into_iter().map(Seed::Addr)).await
461    }
462
463    /// [`join`](Self::join) with seeds that may be DNS names. Each name is looked up now, and
464    /// every address it has is a seed. Names that do not resolve are logged and skipped; while
465    /// none resolves and no seed is an address, they are looked up again with backoff for up to
466    /// `resolve_timeout`, and then the join fails with [`Error::Unresolved`].
467    pub async fn join_seeds(&self, seeds: impl IntoIterator<Item = Seed>) -> Result<usize, Error> {
468        let seeds: Vec<Seed> = seeds.into_iter().collect();
469        let seeds = seed::resolve_for_join(&seeds, self.resolve_timeout)
470            .await
471            .ok_or(Error::Unresolved)?;
472        let rx = self.call(|reply| Request::Join { seeds, reply }).await?;
473        rx.await.unwrap_or(Err(Error::Closed))
474    }
475
476    /// Replaces this node's metadata and gossips it. Returns once the change is queued.
477    pub async fn set_meta(&self, meta: impl Into<Vec<u8>>) -> Result<(), Error> {
478        let meta = meta.into();
479        let rx = self.call(|reply| Request::SetMeta { meta, reply }).await?;
480        rx.await.unwrap_or(Err(Error::Closed))
481    }
482
483    /// Sends `data` to the member `name` at its life `epoch`, or whichever life this node holds
484    /// with epoch 0, and returns once it is handed to the network or queued for a connection.
485    ///
486    /// Best effort: one datagram when it fits, else a TCP connection, never acknowledged or
487    /// retried. Fails with [`Error::MessageTooLarge`] over `max_user_message` and with
488    /// [`Error::NotAMember`] unless this node holds that life of `name` as a live member.
489    /// The receiver hears it as [`Event::UserMessage`].
490    pub async fn send(
491        &self,
492        name: impl Into<String>,
493        epoch: u64,
494        data: impl Into<Vec<u8>>,
495    ) -> Result<(), Error> {
496        let (to, data) = (name.into(), data.into());
497        let rx = self
498            .call(|reply| Request::Send {
499                to,
500                epoch,
501                data,
502                reply,
503            })
504            .await?;
505        rx.await.unwrap_or(Err(Error::Closed))
506    }
507
508    /// Queues `data` to be gossiped to every member, and returns once it is queued.
509    ///
510    /// Best effort, and at most once on each node, which hears it as [`Event::UserMessage`]
511    /// with `broadcast` set; never this node. Fails with [`Error::MessageTooLarge`] over
512    /// `max_user_broadcast`. A full queue drops the broadcast nearest to done and counts it
513    /// in [`Stats`].
514    pub async fn broadcast(&self, data: impl Into<Vec<u8>>) -> Result<(), Error> {
515        let data = data.into();
516        let rx = self
517            .call(|reply| Request::Broadcast { data, reply })
518            .await?;
519        rx.await.unwrap_or(Err(Error::Closed))
520    }
521
522    /// Tells the cluster this node is leaving and waits, up to `timeout`, until the news has
523    /// been sent as often as any rumour is. The node then stops probing and never refutes, so it
524    /// is reported as left, never dead. Call [`close`](Self::close) afterwards.
525    pub async fn leave(&self, timeout: Duration) -> Result<(), Error> {
526        let rx = self.call(|reply| Request::Leave { reply }).await?;
527        match tokio::time::timeout(timeout, rx).await {
528            Ok(r) => r.unwrap_or(Err(Error::Closed)),
529            Err(_) => Err(Error::Timeout),
530        }
531    }
532
533    /// Stops the node and closes its sockets, then returns. Without [`leave`](Self::leave)
534    /// first, peers see a crash. Calling it again does nothing.
535    pub async fn close(&self) {
536        let (reply, done) = oneshot::channel();
537        if self.requests.send(Request::Close { reply }).await.is_ok() {
538            let _ = done.await;
539        }
540        let task = self.task().take();
541        if let Some(task) = task {
542            if let Err(e) = task.await {
543                if e.is_panic() {
544                    tracing::error!("the node's actor panicked");
545                }
546            }
547        }
548    }
549
550    /// Stops the node at once, as if its process crashed: nothing is sent, sockets close, and
551    /// peers detect the failure. For tests and fault injection.
552    pub fn abort(&self) {
553        // Dropping the handle detaches the task; the abort stops it. Taking it also tells Drop
554        // that this stop was deliberate.
555        drop(self.task().take());
556        self.abort.abort();
557    }
558
559    /// Makes the actor panic once it reaches this request, as a bug in it would. Not part of the
560    /// API: it exists for the tests of how a failed node reports itself.
561    #[cfg(any(test, feature = "test-hooks"))]
562    #[doc(hidden)]
563    pub fn panic_actor(&self) {
564        let _ = self.requests.try_send(Request::Panic);
565    }
566
567    /// True once the node has stopped, by [`close`](Self::close), [`abort`](Self::abort) or a
568    /// panic in its actor.
569    pub fn is_closed(&self) -> bool {
570        self.requests.is_closed()
571    }
572
573    async fn call<R>(
574        &self,
575        make: impl FnOnce(oneshot::Sender<R>) -> Request,
576    ) -> Result<oneshot::Receiver<R>, Error> {
577        let (reply, rx) = oneshot::channel();
578        self.requests
579            .send(make(reply))
580            .await
581            .map_err(|_| Error::Closed)?;
582        Ok(rx)
583    }
584
585    fn task(&self) -> std::sync::MutexGuard<'_, Option<JoinHandle<()>>> {
586        self.task.lock().unwrap_or_else(PoisonError::into_inner)
587    }
588}
589
590/// Runtime key rotation for one node, from [`Memberlist::keyring`].
591///
592/// Every call acts on this node only. To rotate a cluster, run each step on every node and wait
593/// for it to finish everywhere before starting the next:
594///
595/// 1. [`install`](Self::install) the new key, so every node can read it;
596/// 2. [`use_key`](Self::use_key) it, so every node sends with it;
597/// 3. [`remove`](Self::remove) the old key.
598///
599/// A node that falls a step behind looks dead to its peers until it catches up, and they count
600/// what they dropped as `decrypt_failures` in [`Stats`]. A node that runs without encryption
601/// refuses every call with [`Error::NotEncrypted`]. Key ids are safe to log; key bytes never are.
602///
603/// A handle is cheap to clone and keeps working only while the node runs: calls on a closed node
604/// fail with [`Error::Closed`].
605#[derive(Clone)]
606pub struct Keyring {
607    requests: mpsc::Sender<Request>,
608    shared: Arc<Shared>,
609}
610
611impl Keyring {
612    /// Adds `key` to the keys this node can decrypt with. Does nothing if it is installed.
613    pub async fn install(&self, key: Key) -> Result<(), Error> {
614        self.change(KeyChange::Install(key)).await
615    }
616
617    /// Makes the installed `key` the one this node encrypts with. Every installed key still
618    /// decrypts. Fails with [`Error::KeyNotInstalled`] for a key that was never installed.
619    ///
620    /// Named `use_key` because `use` is a Rust keyword; Python spells it `use`.
621    pub async fn use_key(&self, key: Key) -> Result<(), Error> {
622        self.change(KeyChange::Use(key)).await
623    }
624
625    /// Drops `key`. Fails with [`Error::KeyInUse`] for the key this node encrypts with and
626    /// [`Error::LastKey`] for the only one; a key that is not installed is already gone.
627    pub async fn remove(&self, key: Key) -> Result<(), Error> {
628        self.change(KeyChange::Remove(key)).await
629    }
630
631    /// The ids of the installed keys, the one this node encrypts with first. Empty when the node
632    /// runs without encryption. Never waits on the actor.
633    pub fn key_ids(&self) -> Vec<KeyId> {
634        self.shared.key_ids.load().as_ref().clone()
635    }
636
637    async fn change(&self, change: KeyChange) -> Result<(), Error> {
638        let (reply, rx) = oneshot::channel();
639        self.requests
640            .send(Request::Keyring { change, reply })
641            .await
642            .map_err(|_| Error::Closed)?;
643        rx.await.unwrap_or(Err(Error::Closed))
644    }
645}
646
647impl fmt::Debug for Keyring {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        f.debug_struct("Keyring")
650            .field("key_ids", &self.key_ids())
651            .finish()
652    }
653}
654
655impl Drop for Memberlist {
656    fn drop(&mut self) {
657        if self.task().is_some() && !self.is_closed() {
658            if self.local().state != State::Left {
659                tracing::warn!("node dropped without leave() or close(); peers will see a crash");
660            }
661            self.abort.abort();
662        }
663    }
664}
665
666impl fmt::Debug for Memberlist {
667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668        f.debug_struct("Memberlist")
669            .field("local_addr", &self.local_addr)
670            .field("closed", &self.is_closed())
671            .finish_non_exhaustive()
672    }
673}
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678
679    #[test]
680    fn exposes_wire_version() {
681        assert_eq!(WIRE_VERSION, 1);
682    }
683
684    #[test]
685    fn default_names_are_valid_and_distinct() {
686        let a = default_name();
687        assert!(!a.is_empty() && a.len() <= 64, "{a}");
688        assert_ne!(a, default_name());
689    }
690
691    #[tokio::test]
692    async fn an_actor_panic_fails_pending_and_later_calls_and_marks_events_failed() {
693        const WAIT: Duration = Duration::from_secs(10);
694        let addr = |port: u8| SocketAddr::from(([10, 0, 0, port], 7946));
695        let net = mem::MemNetwork::new();
696        // A seed that never accepts, so a join with it waits for an answer.
697        let _silent = net.bind(addr(9)).unwrap();
698        let cfg = Config::local(Security::InsecurePlaintext);
699        let transport = net.bind(addr(1)).unwrap();
700        let ml = Memberlist::start(Settings::new(cfg, "a"), transport)
701            .await
702            .unwrap();
703        let mut events = ml.events();
704        let join = ml.join([addr(9)]);
705        tokio::pin!(join);
706        // One poll queues the join ahead of the panic.
707        assert!(
708            tokio::time::timeout(Duration::ZERO, &mut join)
709                .await
710                .is_err()
711        );
712
713        ml.panic_actor();
714        let pending = tokio::time::timeout(WAIT, join).await.unwrap();
715        assert!(matches!(pending, Err(Error::Closed)), "{pending:?}");
716        assert_eq!(
717            tokio::time::timeout(WAIT, events.recv()).await.unwrap(),
718            None
719        );
720        assert!(events.failed());
721        assert!(ml.is_closed());
722        let later = ml.set_meta(b"x".to_vec()).await;
723        assert!(matches!(later, Err(Error::Closed)), "{later:?}");
724        let later = ml.keyring().install(Key::from_bytes([1; 32])).await;
725        assert!(matches!(later, Err(Error::Closed)), "{later:?}");
726        assert!(
727            ml.events().failed(),
728            "a later subscription sees the failure too"
729        );
730        ml.close().await;
731    }
732
733    #[test]
734    fn generated_keys_are_random_and_round_trip() {
735        let a = generate_key().unwrap();
736        let b = generate_key().unwrap();
737        assert_ne!(a, b);
738        assert_eq!(Key::from_base64(&a.to_base64()), Ok(a));
739    }
740}