1mod 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#[derive(Debug, Clone)]
43pub struct Settings {
44 pub core: Config,
46 pub name: String,
48 pub advertise: Option<SocketAddr>,
51 pub meta: Vec<u8>,
53 pub seeds: Vec<SocketAddr>,
56 pub seed_hosts: Vec<Seed>,
61 pub resolve_timeout: Duration,
65 pub max_inbound_streams: usize,
68 pub max_inbound_streams_per_ip: usize,
71 pub max_inbound_bytes_per_ip: Option<usize>,
75 pub tcp_header_timeout: Duration,
78 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 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 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 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
144const FRAME_PREFIX_LEN: usize = 4;
146
147fn 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#[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#[derive(Debug, Clone, Default, PartialEq, Eq)]
168#[non_exhaustive]
169pub struct Stats {
170 pub metrics: Metrics,
171 pub local_health: u32,
173}
174
175#[derive(Debug)]
177#[non_exhaustive]
178pub enum Error {
179 Config(ConfigError),
181 Io(io::Error),
183 JoinFailed,
185 Unresolved,
187 MetaTooLarge,
189 MessageTooLarge,
192 NotAMember,
195 Left,
197 Timeout,
199 Closed,
201 NotEncrypted,
203 KeyNotInstalled,
205 KeyInUse,
207 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
273pub 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
285pub 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
304const REQUEST_QUEUE: usize = 256;
306
307pub 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 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 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 pub fn local_addr(&self) -> SocketAddr {
414 self.local_addr
415 }
416
417 pub fn local(&self) -> Member {
419 self.shared.snapshot.load().local().clone()
420 }
421
422 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 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 pub fn events(&self) -> Events {
441 self.shared.hub.subscribe()
442 }
443
444 pub fn stats(&self) -> Stats {
446 self.shared.stats().clone()
447 }
448
449 pub fn keyring(&self) -> Keyring {
451 Keyring {
452 requests: self.requests.clone(),
453 shared: Arc::clone(&self.shared),
454 }
455 }
456
457 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 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 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 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 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 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 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 pub fn abort(&self) {
553 drop(self.task().take());
556 self.abort.abort();
557 }
558
559 #[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 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#[derive(Clone)]
606pub struct Keyring {
607 requests: mpsc::Sender<Request>,
608 shared: Arc<Shared>,
609}
610
611impl Keyring {
612 pub async fn install(&self, key: Key) -> Result<(), Error> {
614 self.change(KeyChange::Install(key)).await
615 }
616
617 pub async fn use_key(&self, key: Key) -> Result<(), Error> {
622 self.change(KeyChange::Use(key)).await
623 }
624
625 pub async fn remove(&self, key: Key) -> Result<(), Error> {
628 self.change(KeyChange::Remove(key)).await
629 }
630
631 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 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 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}