chiark / gitweb /
make success_report_interval configuraable
[hippotat.git] / src / config.rs
index 69718d7a1ff0e457f270a85938a7ef618ecac316..a36ec584f987864a27bb2803ca553dce9d574dd8 100644 (file)
@@ -1,5 +1,5 @@
 // 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::*;
@@ -10,9 +10,9 @@ use configparser::ini::Ini;
 #[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,
@@ -38,8 +38,12 @@ pub struct InstanceConfig {
   #[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#"
@@ -54,11 +58,12 @@ max_batch_up = 4000
 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
 
@@ -86,6 +91,11 @@ pub struct Opts {
   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)]
@@ -193,9 +203,7 @@ impl FromStr for SectionName {
 }
 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 {
@@ -625,6 +633,13 @@ impl<'c> ResolveContext<'c> {
     }
   }
 
+  #[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 {
@@ -637,8 +652,8 @@ impl<'c> ResolveContext<'c> {
   }
 
   #[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()
   }
 }
 
@@ -660,6 +675,18 @@ impl InstanceConfig {
       )?;
     }
 
+    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>() {
@@ -678,21 +705,89 @@ impl InstanceConfig {
             })
             .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(