// Copyright 2021 Ian Jackson and contributors to Hippotat
-// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-License-Identifier: GPL-3.0-or-later
// There is NO WARRANTY.
use crate::prelude::*;
#[derive(Debug,Clone)]
pub struct InstanceConfig {
// Exceptional settings
- #[special(special_server, SKL::ServerName)] pub server: ServerName,
+ #[special(special_link, SKL::ServerName)] pub link: LinkName,
pub secret: Secret,
- #[special(special_ipif, SKL::Ordinary)] pub ipif: String,
+ #[special(special_ipif, SKL::Ordinary)] pub ipif: String,
// Capped settings:
#[limited] pub max_batch_down: u32,
#[client] pub max_requests_outstanding: u32,
#[client] pub max_batch_up: u32,
#[client] pub http_retry: Duration,
+ #[client] pub success_report_interval: Duration,
#[client] pub url: Uri,
#[client] pub vroutes: Vec<IpNet>,
+
+ // Computed, rather than looked up. Client only:
+ #[computed] pub effective_http_timeout: Duration,
}
static DEFAULT_CONFIG: &str = r#"
http_retry = 5
port = 80
vroutes = ''
-ifname_client = hippo%%d
-ifname_server = shippo%%d
+ifname_client = hippo%d
+ifname_server = shippo%d
max_clock_skew = 300
+success_report_interval = 3600
-ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s %(rnets)s
+ipif = userv root ipif %{local},%{peer},%{mtu},slip,%{ifname} '%{rnets}'
mtu = 1500
pub extra_config: Vec<PathBuf>,
}
+#[ext(pub)]
+impl u32 {
+ fn sat(self) -> usize { self.try_into().unwrap_or(usize::MAX) }
+}
+
#[ext]
impl<'s> Option<&'s str> {
#[throws(AE)]
}
impl Display for InstanceConfig {
#[throws(fmt::Error)]
- fn fmt(&self, f: &mut fmt::Formatter) {
- write!(f, "[{} {}]", &self.server, &self.vaddr)?;
- }
+ fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.link, f)? }
}
impl Display for SectionName {
}
}
+ #[throws(AE)]
+ pub fn computed<T>(&self, _key: &'static str) -> T
+ where T: Default
+ {
+ default()
+ }
+
#[throws(AE)]
pub fn special_ipif(&self, key: &'static str) -> String {
match self.end {
}
#[throws(AE)]
- pub fn special_server(&self, _key: &'static str) -> ServerName {
- self.link.server.clone()
+ pub fn special_link(&self, _key: &'static str) -> LinkName {
+ self.link.clone()
}
}
)?;
}
+ let check_batch = {
+ let mtu = self.mtu;
+ move |max_batch, key| {
+ if max_batch/2 < mtu {
+ throw!(anyhow!("max batch {:?} ({}) must be >= 2 x mtu ({}) \
+ (to allow for SLIP ESC-encoding)",
+ key, max_batch, mtu))
+ }
+ Ok::<_,AE>(())
+ }
+ };
+
match end {
LinkEnd::Client => {
if &self.url == &default::<Uri>() {
})
.parse().unwrap()
}
+
+ self.effective_http_timeout = {
+ let a = self.http_timeout;
+ let b = self.http_timeout_grace;
+ a.checked_add(b).ok_or_else(
+ || anyhow!("calculate effective http timeout ({:?} + {:?})", a, b)
+ )?
+ };
+
+ check_batch(self.max_batch_up, "max_batch_up")?;
},
LinkEnd::Server => {
if self.addrs.is_empty() {
throw!(anyhow!("missing 'addrs' setting"))
}
+ check_batch(self.max_batch_down, "max_batch_down")?;
},
+
+ // xxx check target vs max req outstanding
+ }
+
+ #[throws(AE)]
+ fn subst(var: &mut String,
+ kv: &mut dyn Iterator<Item=(&'static str, &dyn Display)>
+ ) {
+ let substs = kv
+ .map(|(k,v)| (k.to_string(), v.to_string()))
+ .collect::<HashMap<String, String>>();
+ let bad = parking_lot::Mutex::new(vec![]);
+ *var = regex_replace_all!(
+ r#"%(?:%|\((\w+)\)s|\{(\w+)\}|.)"#,
+ &var,
+ |whole, k1, k2| (|| Ok::<_,String>({
+ if whole == "%%" { "%" }
+ else if let Some(&k) = [k1,k2].iter().find(|&&s| s != "") {
+ substs.get(k).ok_or_else(
+ || format!("unknown key %({})s", k)
+ )?
+ } else {
+ throw!(format!("bad percent escape {:?}", &whole));
+ }
+ }))().unwrap_or_else(|e| { bad.lock().push(e); "" })
+ ).into_owned();
+ let bad = bad.into_inner();
+ if ! bad.is_empty() {
+ throw!(anyhow!("substitution failed: {}", bad.iter().format("; ")));
+ }
+ }
+
+ {
+ use LinkEnd::*;
+ type DD<'d> = &'d dyn Display;
+ fn dv<T:Display>(v: &[T]) -> String {
+ format!("{}", v.iter().format(" "))
+ }
+ let mut ipif = mem::take(&mut self.ipif); // lets us borrow all of self
+ let s = &self; // just for abbreviation, below
+ let vnetwork = dv(&s.vnetwork);
+ let vroutes = dv(&s.vroutes);
+
+ let keys = &["local", "peer", "rnets", "ifname"];
+ let values = match end {
+ Server => [&s.vaddr as DD , &s.vrelay, &vnetwork, &s.ifname_server],
+ Client => [&s.link.client as DD, &s.vaddr, &vroutes, &s.ifname_client],
+ };
+ let always = [
+ ( "mtu", &s.mtu as DD ),
+ ];
+
+ subst(
+ &mut ipif,
+ &mut keys.iter().cloned()
+ .zip_eq(values)
+ .chain(always.iter().cloned()),
+ ).context("ipif")?;
+ self.ipif = ipif;
}
}
}
#[throws(AE)]
-pub fn read(end: LinkEnd) -> Vec<InstanceConfig> {
- let opts = config::Opts::from_args();
-
+pub fn read(opts: &Opts, end: LinkEnd) -> Vec<InstanceConfig> {
let agg = (||{
let mut agg = Aggregate::default();
agg.keys_allowed.extend(