use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration;
use kinship_net::{ConfigError, Key, Security, Settings};
#[derive(Debug, Clone)]
pub struct Config {
core: kinship_net::Config,
name: Option<String>,
bind: SocketAddr,
advertise: Option<SocketAddr>,
meta: Vec<u8>,
seeds: Vec<SocketAddr>,
keys: Vec<Key>,
insecure_plaintext: bool,
loopback_plaintext: bool,
max_inbound_streams: usize,
max_inbound_streams_per_ip: usize,
max_inbound_bytes_per_ip: Option<usize>,
tcp_header_timeout: Duration,
event_buffer: usize,
}
pub const DEFAULT_PORT: u16 = 7946;
macro_rules! core_setters {
($($(#[$doc:meta])* $with:ident => $($field:ident).+ : $ty:ty;)*) => {
$(
$(#[$doc])*
pub fn $with(mut self, value: $ty) -> Self {
self.core.$($field).+ = value;
self
}
)*
};
}
impl Config {
fn preset(core: kinship_net::Config, bind: SocketAddr, loopback_plaintext: bool) -> Self {
Self {
core,
name: None,
bind,
advertise: None,
meta: Vec::new(),
seeds: Vec::new(),
keys: Vec::new(),
insecure_plaintext: false,
loopback_plaintext,
max_inbound_streams: Settings::DEFAULT_MAX_INBOUND_STREAMS,
max_inbound_streams_per_ip: Settings::DEFAULT_MAX_INBOUND_STREAMS_PER_IP,
max_inbound_bytes_per_ip: None,
tcp_header_timeout: Settings::DEFAULT_TCP_HEADER_TIMEOUT,
event_buffer: Settings::DEFAULT_EVENT_BUFFER,
}
}
pub fn lan() -> Self {
let bind = (Ipv4Addr::UNSPECIFIED, DEFAULT_PORT).into();
Self::preset(
kinship_net::Config::lan(Security::InsecurePlaintext),
bind,
false,
)
}
pub fn wan() -> Self {
let bind = (Ipv4Addr::UNSPECIFIED, DEFAULT_PORT).into();
Self::preset(
kinship_net::Config::wan(Security::InsecurePlaintext),
bind,
false,
)
}
pub fn local() -> Self {
let bind = (Ipv4Addr::LOCALHOST, DEFAULT_PORT).into();
Self::preset(
kinship_net::Config::local(Security::InsecurePlaintext),
bind,
true,
)
}
pub fn core(&self) -> &kinship_net::Config {
&self.core
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn bind(&self) -> SocketAddr {
self.bind
}
pub fn seeds(&self) -> &[SocketAddr] {
&self.seeds
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_bind(mut self, bind: SocketAddr) -> Self {
self.bind = bind;
self
}
pub fn with_advertise(mut self, advertise: SocketAddr) -> Self {
self.advertise = Some(advertise);
self
}
pub fn with_cluster(mut self, cluster: impl Into<String>) -> Self {
self.core.cluster = cluster.into();
self
}
pub fn with_meta(mut self, meta: impl Into<Vec<u8>>) -> Self {
self.meta = meta.into();
self
}
pub fn with_seeds(mut self, seeds: impl IntoIterator<Item = SocketAddr>) -> Self {
self.seeds = seeds.into_iter().collect();
self
}
pub fn with_keys(mut self, keys: impl IntoIterator<Item = Key>) -> Self {
self.keys = keys.into_iter().collect();
self
}
pub fn with_insecure_plaintext(mut self, insecure: bool) -> Self {
self.insecure_plaintext = insecure;
self
}
pub fn with_max_inbound_streams(mut self, n: usize) -> Self {
self.max_inbound_streams = n;
self
}
pub fn with_max_inbound_streams_per_ip(mut self, n: usize) -> Self {
self.max_inbound_streams_per_ip = n;
self
}
pub fn with_max_inbound_bytes_per_ip(mut self, bytes: usize) -> Self {
self.max_inbound_bytes_per_ip = Some(bytes);
self
}
pub fn with_tcp_header_timeout(mut self, timeout: Duration) -> Self {
self.tcp_header_timeout = timeout;
self
}
pub fn with_event_buffer(mut self, n: usize) -> Self {
self.event_buffer = n;
self
}
pub fn without_lifeguard(mut self) -> Self {
self.core = self.core.without_lifeguard();
self
}
core_setters! {
with_probe_interval => probe_interval: Duration;
with_probe_timeout => probe_timeout: Duration;
with_indirect_checks => indirect_checks: usize;
with_tcp_fallback_ping => tcp_fallback_ping: bool;
with_suspicion_mult => suspicion_mult: u32;
with_suspicion_max_mult => suspicion_max_mult: u32;
with_expected_confirmations => expected_confirmations: u32;
with_awareness_max => awareness_max: u32;
with_local_health => local_health: bool;
with_nacks => nacks: bool;
with_dynamic_suspicion => dynamic_suspicion: bool;
with_buddy_system => buddy_system: bool;
with_dead_reclaim => dead_reclaim: Duration;
with_gossip_interval => gossip_interval: Duration;
with_gossip_nodes => gossip_nodes: usize;
with_gossip_to_the_dead => gossip_to_the_dead: Duration;
with_retransmit_mult => retransmit_mult: u32;
with_push_pull_interval => push_pull_interval: Duration;
with_reconnect_interval => reconnect_interval: Duration;
with_rejoin_interval => rejoin_interval: Duration;
with_tcp_timeout => tcp_timeout: Duration;
with_join_retries => join_retries: u32;
with_max_members => max_members: usize;
with_max_new_members_per_packet => max_new_members_per_packet: usize;
with_udp_max_payload => limits.udp_max_payload: usize;
with_max_stream_frame => limits.max_stream_frame: usize;
with_max_meta_bytes => limits.max_meta_bytes: usize;
}
pub(crate) fn build(self) -> Result<(Settings, SocketAddr), ConfigError> {
let mut core = self.core;
core.security = if !self.keys.is_empty() {
Security::Keys(self.keys)
} else if self.insecure_plaintext {
tracing::warn!("running without encryption: insecure_plaintext is set");
Security::InsecurePlaintext
} else if self.loopback_plaintext && self.bind.ip().is_loopback() {
Security::InsecurePlaintext
} else {
return Err(ConfigError {
field: "keys",
reason: "are required unless insecure_plaintext is set, or local() binds loopback",
});
};
core.validate()?;
let name = self.name.unwrap_or_else(kinship_net::default_name);
kinship_net::Identity::new(name.as_str(), self.bind)?;
let settings = Settings {
core,
name,
advertise: self.advertise,
meta: self.meta,
seeds: self.seeds,
max_inbound_streams: self.max_inbound_streams,
max_inbound_streams_per_ip: self.max_inbound_streams_per_ip,
max_inbound_bytes_per_ip: self.max_inbound_bytes_per_ip,
tcp_header_timeout: self.tcp_header_timeout,
event_buffer: self.event_buffer,
};
settings.validate()?;
if settings.meta.len() > settings.core.limits.max_meta_bytes {
return Err(ConfigError {
field: "meta",
reason: "must be at most max_meta_bytes",
});
}
Ok((settings, self.bind))
}
}
impl Default for Config {
fn default() -> Self {
Self::lan()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keys_are_required_beyond_loopback() {
let err = Config::lan().build().unwrap_err();
assert_eq!(err.field, "keys");
let lan = Config::lan().with_bind("127.0.0.1:0".parse().unwrap());
assert_eq!(
lan.build().unwrap_err().field,
"keys",
"only local() waives keys"
);
let (s, _) = Config::local().build().unwrap();
assert_eq!(s.core.security, Security::InsecurePlaintext);
let wide = Config::local().with_bind("0.0.0.0:0".parse().unwrap());
assert_eq!(wide.clone().build().unwrap_err().field, "keys");
assert!(wide.with_insecure_plaintext(true).build().is_ok());
let key = Key::from_bytes([1; 32]);
let (s, bind) = Config::lan().with_keys([key.clone()]).build().unwrap();
assert_eq!(s.core.security, Security::Keys(vec![key]));
assert_eq!(bind, "0.0.0.0:7946".parse().unwrap());
}
#[test]
fn bad_fields_are_named_before_binding() {
let cfg = Config::local().with_probe_timeout(Duration::from_secs(5));
assert_eq!(cfg.build().unwrap_err().field, "probe_timeout");
let cfg = Config::local().with_name("x".repeat(65));
assert_eq!(cfg.build().unwrap_err().field, "name");
let cfg = Config::local().with_event_buffer(0);
assert_eq!(cfg.build().unwrap_err().field, "event_buffer");
let cfg = Config::local().with_meta(vec![0; 513]);
assert_eq!(cfg.build().unwrap_err().field, "meta");
let cfg = Config::local().with_udp_max_payload(576);
assert_eq!(cfg.build().unwrap_err().field, "udp_max_payload");
let cfg = Config::local().with_max_meta_bytes(4096);
assert_eq!(cfg.build().unwrap_err().field, "max_meta_bytes");
let cfg = Config::local().with_max_inbound_streams_per_ip(0);
assert_eq!(cfg.build().unwrap_err().field, "max_inbound_streams_per_ip");
let frame = Config::local().core().limits.max_stream_frame;
let cfg = Config::local().with_max_inbound_bytes_per_ip(frame + 3);
assert_eq!(cfg.build().unwrap_err().field, "max_inbound_bytes_per_ip");
assert!(
Config::local()
.with_max_inbound_bytes_per_ip(frame + 4)
.build()
.is_ok()
);
let cfg = Config::local().with_tcp_header_timeout(Duration::ZERO);
assert_eq!(cfg.build().unwrap_err().field, "tcp_header_timeout");
let cfg = Config::local().with_max_members(0);
assert_eq!(cfg.build().unwrap_err().field, "max_members");
let cfg = Config::local().with_max_new_members_per_packet(0);
assert_eq!(cfg.build().unwrap_err().field, "max_new_members_per_packet");
}
#[test]
fn setters_reach_the_core_and_lifeguard_stays_reachable() {
let cfg = Config::wan()
.with_cluster("edge")
.with_nacks(false)
.with_buddy_system(false)
.with_max_meta_bytes(256)
.with_rejoin_interval(Duration::ZERO);
let core = cfg.core();
assert_eq!(core.cluster, "edge");
assert!(!core.nacks && !core.buddy_system);
assert!(core.local_health && core.dynamic_suspicion);
assert_eq!(core.limits.max_meta_bytes, 256);
assert_eq!(core.probe_interval, Duration::from_secs(5));
assert_eq!(
(core.max_members, core.max_new_members_per_packet),
(8_192, 1_024)
);
let big = Config::wan()
.with_max_members(20_001)
.with_max_new_members_per_packet(20_000);
let core = big.core();
assert_eq!(
(core.max_members, core.max_new_members_per_packet),
(20_001, 20_000)
);
let plain = cfg.without_lifeguard();
let core = plain.core();
assert!(!core.local_health && !core.nacks && !core.dynamic_suspicion && !core.buddy_system);
}
}