chiark / gitweb /
Merge branch 'release' into 'main'
[hippotat.git] / src / config.rs
1 // Copyright 2021-2022 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 #[derive(hippotat_macros::ResolveConfig)]
8 #[derive(Debug,Clone)]
9 pub struct InstanceConfig {
10   // Exceptional settings
11   #[special(special_link, SKL::None)]      pub    link:   LinkName,
12   #[per_client]                            pub    secret: Secret,
13   #[global] #[special(special_ipif, SKL::PerClient)] pub ipif: String,
14
15   // Capped settings:
16   #[limited] pub max_batch_down:               u32,
17   #[limited] pub max_queue_time:               Duration,
18   #[limited] pub http_timeout:                 Duration,
19   #[limited] pub target_requests_outstanding:  u32,
20   #[special(special_max_up, SKL::Limited)]  pub max_batch_up: u32,
21
22   // Ordinary settings, used by both, not client-specifi:
23   #[global]  pub addrs:                        Vec<IpAddr>,
24   #[global]  pub vnetwork:                     Vec<IpNet>,
25   #[global]  pub vaddr:                        IpAddr,
26   #[global]  pub vrelay:                       IpAddr,
27   #[global]  pub port:                         u16,
28   #[global]  pub mtu:                          u32,
29
30   // Ordinary settings, used by server only:
31   #[server] #[per_client] pub max_clock_skew:               Duration,
32   #[server] #[global]     pub ifname_server:                String,
33
34   // Ordinary settings, used by client only:
35   #[client]  pub http_timeout_grace:           Duration,
36   #[client]  pub max_requests_outstanding:     u32,
37   #[client]  pub http_retry:                   Duration,
38   #[client]  pub success_report_interval:      Duration,
39   #[client]  pub url:                          Url,
40   #[client]  pub vroutes:                      Vec<IpNet>,
41   #[client]  pub ifname_client:                String,
42
43   // Computed, rather than looked up.  Client only:
44   #[computed]  pub effective_http_timeout:     Duration,
45 }
46
47 static DEFAULT_CONFIG: &str = r#"
48 [COMMON]
49 max_batch_down = 65536
50 max_queue_time = 10
51 target_requests_outstanding = 3
52 http_timeout = 30
53 http_timeout_grace = 5
54 max_requests_outstanding = 6
55 max_batch_up = 4000
56 http_retry = 5
57 port = 80
58 vroutes = ''
59 ifname_client = hippo%d
60 ifname_server = shippo%d
61 max_clock_skew = 300
62 success_report_interval = 3600
63
64 ipif = userv root ipif %{local},%{peer},%{mtu},slip,%{ifname} '%{rnets}'
65
66 mtu = 1500
67
68 vnetwork = 172.24.230.192
69
70 [LIMIT]
71 max_batch_up = 262144
72 max_batch_down = 262144
73 max_queue_time = 121
74 http_timeout = 121
75 target_requests_outstanding = 10
76 "#;
77
78 #[derive(clap::Args,Debug)]
79 pub struct CommonOpts {
80   /// Top-level config file or directory
81   ///
82   /// Look for `main.cfg`, `config.d` and `secrets.d` here.
83   ///
84   /// Or if this is a file, just read that file.
85   #[clap(long, default_value="/etc/hippotat")]
86   pub config: PathBuf,
87   
88   /// Additional config files or dirs, which can override the others
89   #[clap(long, action=clap::ArgAction::Append)]
90   pub extra_config: Vec<PathBuf>,
91 }
92
93 pub trait InspectableConfigAuto {
94   fn inspect_key_auto(&self, field: &'_ str)
95                       -> Option<&dyn InspectableConfigValue>;
96 }
97 pub trait InspectableConfig: Debug {
98   fn inspect_key(&self, field: &'_ str)
99                  -> Option<&dyn InspectableConfigValue>;
100 }
101
102 impl InspectableConfig for (&ServerName, &InstanceConfigGlobal) {
103   fn inspect_key(&self, field: &'_ str)
104                  -> Option<&dyn InspectableConfigValue> {
105     Some(match field {
106       "server" => self.0,
107       k => return self.1.inspect_key_auto(k),
108     })
109   }
110 }
111
112 impl InspectableConfig for InstanceConfig {
113   fn inspect_key(&self, field: &'_ str)
114                  -> Option<&dyn InspectableConfigValue> {
115     Some(match field {
116       "link" => &self.link,
117       "server" => &self.link.server,
118       "client" => &self.link.client,
119       k => return self.inspect_key_auto(k),
120     })
121   }
122 }
123
124 #[derive(Debug,Clone,Copy)]
125 pub struct PrintConfigOpt<'a>(pub &'a Option<String>);
126
127 impl PrintConfigOpt<'_> {
128   #[throws(AE)]
129   pub fn implement<'c, C: InspectableConfig + 'c>(
130     self,
131     configs: impl IntoIterator<Item=&'c C>,
132   ) {
133     if let Some(arg) = self.0 {
134       for config in configs {
135         Self::print_one_config(arg, config)?;
136       }
137       process::exit(0);
138     }
139   }
140
141   pub fn keys(&self) -> impl Iterator<Item=&str> {
142     self.0.as_ref().map(|arg| Self::split(arg)).into_iter().flatten()
143   }
144
145   fn split(arg: &str) -> impl Iterator<Item=&str> { arg.split(',') }
146
147   #[throws(AE)]
148   fn print_one_config(
149     arg: &str,
150     config: &dyn InspectableConfig,
151   ) {
152     let output = Self::split(arg)
153       .map(|key| {
154         if key == "pretty" {
155           return Ok(format!("{:#?}", &config));
156         }
157         let insp = config.inspect_key(key)
158           .ok_or_else(|| anyhow!("unknown config key {:?}", key))?;
159         Ok::<_,AE>(DisplayInspectable(insp).to_string())
160       })
161       .collect::<Result<Vec<_>,_>>()?
162       .join("\t");
163     println!("{}",  output);
164   }
165 }
166
167 pub trait InspectableConfigValue {
168   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
169 }
170 #[macro_export]
171 macro_rules! impl_inspectable_config_value {
172   { $t:ty as $trait:path } => {
173     impl InspectableConfigValue for $t {
174       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175         <Self as $trait>::fmt(self, f)
176       }
177     }
178   };
179   
180   { Vec<$t:ty> } => {
181     impl InspectableConfigValue for Vec<$t> {
182       #[throws(fmt::Error)]
183       fn fmt(&self, f: &mut fmt::Formatter) {
184         let mut first = Some(());
185         for v in self.iter() {
186           if first.take().is_none() { write!(f, " ")?; }
187           InspectableConfigValue::fmt(v, f)?;
188         }
189       }
190     }
191   };
192 }
193
194 pub struct DisplayInspectable<'i>(pub &'i dyn InspectableConfigValue);
195 impl<'i> Display for DisplayInspectable<'i> {
196   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197     InspectableConfigValue::fmt(self.0, f)
198   }
199 }
200
201 impl_inspectable_config_value!{ String as Display }
202 impl_inspectable_config_value!{ ServerName as Display }
203 impl_inspectable_config_value!{ ClientName as Display }
204 impl_inspectable_config_value!{ u16 as Display }
205 impl_inspectable_config_value!{ u32 as Display }
206 impl_inspectable_config_value!{ reqwest::Url as Display }
207
208 impl_inspectable_config_value!{ IpAddr as Display }
209 impl_inspectable_config_value!{ ipnet::IpNet as Display }
210 impl_inspectable_config_value!{ Vec<IpAddr> }
211 impl_inspectable_config_value!{ Vec<ipnet::IpNet> }
212
213 impl InspectableConfigValue for Duration {
214   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215     let v = self.as_secs_f64();
216     Display::fmt(&v, f)
217   }
218 }
219
220 #[ext(U32Ext)]
221 pub impl u32 {
222   fn sat(self) -> usize { self.try_into().unwrap_or(usize::MAX) }
223 }
224
225 #[ext]
226 impl<'s> Option<&'s str> {
227   #[throws(AE)]
228   fn value(self) -> &'s str {
229     self.ok_or_else(|| anyhow!("value needed"))?
230   }
231 }
232
233 #[derive(Clone)]
234 pub struct Secret(pub String);
235 impl Parseable for Secret {
236   #[throws(AE)]
237   fn parse(s: Option<&str>) -> Self {
238     let s = s.value()?;
239     if s.is_empty() { throw!(anyhow!("secret value cannot be empty")) }
240     Secret(s.into())
241   }
242   #[throws(AE)]
243   fn default_for_ordinary() -> Self { Parseable::unspecified() }
244   fn unspecified() -> Self { Secret(default()) }
245 }
246 impl Debug for Secret {
247   #[throws(fmt::Error)]
248   fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Secret(***)")? }
249 }
250 impl_inspectable_config_value!{ Secret as Debug }
251
252 #[derive(Debug,Clone,Hash,Eq,PartialEq)]
253 pub enum SectionName {
254   Link(LinkName),
255   Client(ClientName),
256   Server(ServerName), // includes SERVER, which is slightly special
257   ServerLimit(ServerName),
258   GlobalLimit,
259   Common,
260 }
261 pub use SectionName as SN;
262
263 #[derive(Debug)]
264 struct RawValRef<'v,'l,'s> {
265   raw: Option<&'v str>, // todo: not Option any more
266   key: &'static str,
267   loc: &'l ini::Loc,
268   section: &'s SectionName,
269 }
270
271 impl<'v> RawValRef<'v,'_,'_> {
272   #[throws(AE)]
273   fn try_map<F,T>(&self, f: F) -> T
274   where F: FnOnce(Option<&'v str>) -> Result<T, AE> {
275     f(self.raw)
276       .with_context(|| format!(r#"file {:?}, section {}, key "{}""#,
277                                self.loc, self.section, self.key))?
278   }
279 }
280
281 pub struct Config {
282   pub opts: CommonOpts,
283 }
284
285 static OUTSIDE_SECTION: &str = "[";
286 static SPECIAL_SERVER_SECTION: &str = "SERVER";
287
288 #[derive(Debug)]
289 struct Aggregate {
290   end: LinkEnd,
291   keys_allowed: HashMap<&'static str, SectionKindList>,
292   sections: HashMap<SectionName, ini::Section>,
293 }
294
295 type OkAnyway<'f,A> = &'f dyn Fn(&io::Error) -> Option<A>;
296 #[ext]
297 impl<'f,A> OkAnyway<'f,A> {
298   fn ok<T>(self, r: &Result<T, io::Error>) -> Option<A> {
299     let e = r.as_ref().err()?;
300     let a = self(e)?;
301     Some(a)
302   }
303 }
304
305 impl FromStr for SectionName {
306   type Err = AE;
307   #[throws(AE)]
308   fn from_str(s: &str) -> Self {
309     match s {
310       "COMMON" => return SN::Common,
311       "LIMIT" => return SN::GlobalLimit,
312       _ => { }
313     };
314     if let Ok(n@ ServerName(_)) = s.parse() { return SN::Server(n) }
315     if let Ok(n@ ClientName(_)) = s.parse() { return SN::Client(n) }
316     let (server, client) = s.split_ascii_whitespace().collect_tuple()
317       .ok_or_else(|| anyhow!(
318         "bad section name {:?} \
319          (must be COMMON, <server>, <client>, or <server> <client>",
320         s
321       ))?;
322     let server = server.parse().context("server name in link section name")?;
323     if client == "LIMIT" { return SN::ServerLimit(server) }
324     let client = client.parse().context("client name in link section name")?;
325     SN::Link(LinkName { server, client })
326   }
327 }
328 impl Display for InstanceConfig {
329   #[throws(fmt::Error)]
330   fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.link, f)? }
331 }
332
333 impl Display for SectionName {
334   #[throws(fmt::Error)]
335   fn fmt(&self, f: &mut fmt::Formatter) {
336     match self {
337       SN::Link  (ref l)      => Display::fmt(l, f)?,
338       SN::Client(ref c)      => write!(f, "[{}]"       , c)?,
339       SN::Server(ref s)      => write!(f, "[{}]"       , s)?,
340       SN::ServerLimit(ref s) => write!(f, "[{} LIMIT] ", s)?,
341       SN::GlobalLimit        => write!(f, "[LIMIT]"       )?,
342       SN::Common             => write!(f, "[COMMON]"      )?,
343     }
344   }
345 }
346
347 impl Aggregate {
348   fn new(
349     end: LinkEnd,
350     keys_allowed: HashMap<&'static str, SectionKindList>
351   ) -> Self { Aggregate {
352     end, keys_allowed,
353     sections: default(),
354   } }
355
356   #[throws(AE)] // AE does not include path
357   fn read_file<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
358   {
359     let f = fs::File::open(path);
360     if let Some(anyway) = anyway.ok(&f) { return Some(anyway) }
361     let mut f = f.context("open")?;
362
363     let mut s = String::new();
364     let y = f.read_to_string(&mut s);
365     if let Some(anyway) = anyway.ok(&y) { return Some(anyway) }
366     y.context("read")?;
367
368     self.read_string(s, path)?;
369     None
370   }
371
372   #[throws(AE)] // AE does not include path
373   fn read_string(&mut self, s: String, path_for_loc: &Path) {
374     let mut map: ini::Parsed = default();
375     ini::read(&mut map, &mut s.as_bytes(), path_for_loc)
376       .context("parse as INI")?;
377     if map.get(OUTSIDE_SECTION).is_some() {
378       throw!(anyhow!("INI file contains settings outside a section"));
379     }
380
381     for (sn, section) in map {
382       let sn = sn.parse().dcontext(&sn)?;
383       let vars = &section.values;
384
385       for (key, val) in vars {
386         (||{
387           let skl = if key == "server" {
388             SKL::ServerName
389           } else {
390             *self.keys_allowed.get(key.as_str()).ok_or_else(
391               || anyhow!("unknown configuration key")
392             )?
393           };
394           if ! skl.contains(&sn, self.end) {
395             throw!(anyhow!("key not applicable in this kind of section"))
396           }
397           Ok::<_,AE>(())
398         })()
399           .with_context(|| format!("key {:?}", key))
400           .with_context(|| val.loc.to_string())?
401       }
402
403       let ent = self.sections.entry(sn)
404         .or_insert_with(|| ini::Section {
405           loc: section.loc.clone(),
406           values: default(),
407         });
408
409       for (key, ini::Val { val: raw, loc }) in vars {
410         let val = if raw.starts_with('\'') || raw.starts_with('"') {
411           (||{
412             if raw.contains('\\') {
413               throw!(
414                 anyhow!("quoted value contains backslash, not supported")
415               );
416             }
417             let quote = &raw[0..1];
418
419             let unq = raw[1..].strip_suffix(quote)
420               .ok_or_else(
421                 || anyhow!("mismatched quotes around quoted value")
422               )?
423               .to_owned();
424             if unq.contains(quote) {
425               throw!(anyhow!(
426                 "quoted value contains quote (escaping not supported)"
427               ))
428             }
429
430             Ok::<_,AE>(unq)
431           })()
432             .with_context(|| format!("key {:?}", key))
433             .with_context(|| loc.to_string())?
434         } else {
435           raw.clone()
436         };
437         let key = key.replace('-',"_");
438         ent.values.insert(key, ini::Val { val, loc: loc.clone() });
439       }
440     }
441   }
442
443   #[throws(AE)] // AE includes path
444   fn read_dir_d<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
445   {
446     let dir = fs::read_dir(path);
447     if let Some(anyway) = anyway.ok(&dir) { return Some(anyway) }
448     let dir = dir.context("open directory").dcontext(path)?;
449     for ent in dir {
450       let ent = ent.context("read directory").dcontext(path)?;
451       let leaf = ent.file_name();
452       let leaf = leaf.to_str();
453       let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8?
454       if leaf.len() == 0 { continue }
455       if ! leaf.chars().all(
456         |c| c=='-' || c=='_' || c.is_ascii_alphanumeric()
457       ) { continue }
458
459       // OK we want this one
460       let ent = ent.path();
461       self.read_file(&ent, &|_| None::<Void>).dcontext(&ent)?;
462     }
463     None
464   }
465
466   #[throws(AE)] // AE includes everything
467   fn read_toplevel(&mut self, toplevel: &Path) {
468     enum Anyway { None, Dir }
469     match self.read_file(toplevel, &|e| match e {
470       e if e.kind() == EK::NotFound => Some(Anyway::None),
471       e if e.is_is_a_directory() => Some(Anyway::Dir),
472       _ => None,
473     })
474       .dcontext(toplevel).context("top-level config directory (or file)")?
475     {
476       None | Some(Anyway::None) => { },
477
478       Some(Anyway::Dir) => {
479         struct AnywayNone;
480         let anyway_none = |e: &io::Error| match e {
481           e if e.kind() == EK::NotFound => Some(AnywayNone),
482           _ => None,
483         };
484
485         let mk = |leaf: &str| {
486           [ toplevel, &PathBuf::from(leaf) ]
487             .iter().collect::<PathBuf>()
488         };
489
490         for &(try_main, desc) in &[
491           ("main.cfg", "main config file"),
492           ("master.cfg", "obsolete-named main config file"),
493         ] {
494           let main = mk(try_main);
495
496           match self.read_file(&main, &anyway_none)
497             .dcontext(main).context(desc)?
498           {
499             None => break,
500             Some(AnywayNone) => { },
501           }
502         }
503
504         for &(try_dir, desc) in &[
505           ("config.d", "per-link config directory"),
506           ("secrets.d", "per-link secrets directory"),
507         ] {
508           let dir = mk(try_dir);
509           match self.read_dir_d(&dir, &anyway_none).context(desc)? {
510             None => { },
511             Some(AnywayNone) => { },
512           }
513         }
514       }
515     }
516   }
517
518   #[throws(AE)] // AE includes extra, but does that this is extra
519   fn read_extra(&mut self, extra: &Path) {
520     struct AnywayDir;
521
522     match self.read_file(extra, &|e| match e {
523       e if e.is_is_a_directory() => Some(AnywayDir),
524       _ => None,
525     })
526       .dcontext(extra)?
527     {
528       None => return,
529       Some(AnywayDir) => {
530         self.read_dir_d(extra, &|_| None::<Void>)?;
531       }
532     }
533
534   }
535 }
536
537 impl Aggregate {
538   fn instances(&self, only_server: Option<&ServerName>) -> BTreeSet<LinkName> {
539     let mut links:              BTreeSet<LinkName> = default();
540
541     let mut secrets_anyserver:  BTreeSet<&ClientName> = default();
542     let mut secrets_anyclient:  BTreeSet<&ServerName> = default();
543     let mut secret_global       = false;
544
545     let mut putative_servers   = BTreeSet::new();
546     let mut putative_clients   = BTreeSet::new();
547
548     let mut note_server = |s| {
549       if let Some(only) = only_server { if s != only { return false } }
550       putative_servers.insert(s);
551       true
552     };
553     let mut note_client = |c| {
554       putative_clients.insert(c);
555     };
556
557     for (section, vars) in &self.sections {
558       let has_secret = || vars.values.contains_key("secret");
559       //dbg!(&section, has_secret());
560
561       match section {
562         SN::Link(l) => {
563           if ! note_server(&l.server) { continue }
564           note_client(&l.client);
565           if has_secret() { links.insert(l.clone()); }
566         },
567         SN::Server(ref s) => {
568           if ! note_server(s) { continue }
569           if has_secret() { secrets_anyclient.insert(s); }
570         },
571         SN::Client(ref c) => {
572           note_client(c);
573           if has_secret() { secrets_anyserver.insert(c); }
574         },
575         SN::Common => {
576           if has_secret() { secret_global = true; }
577         },
578         _ => { },
579       }
580     }
581
582     //dbg!(&putative_servers, &putative_clients);
583     //dbg!(&secrets_anyserver, &secrets_anyclient, &secret_global);
584
585     // Add links which are justified by blanket secrets
586     for (client, server) in iproduct!(
587       putative_clients.into_iter().filter(
588         |c| secret_global
589          || secrets_anyserver.contains(c)
590          || ! secrets_anyclient.is_empty()
591       ),
592       putative_servers.iter().cloned().filter(
593         |s| secret_global
594          || secrets_anyclient.contains(s)
595          || ! secrets_anyserver.is_empty()
596       )
597     ) {
598       links.insert(LinkName {
599         client: client.clone(),
600         server: server.clone(),
601       });
602     }
603
604     links
605   }
606 }
607
608 struct ResolveContext<'c> {
609   agg: &'c Aggregate,
610   link: &'c LinkName,
611   end: LinkEnd,
612   all_sections: Vec<SectionName>,
613 }
614
615 trait Parseable: Sized {
616   fn parse(s: Option<&str>) -> Result<Self, AE>;
617
618   /// Used for lookups with [`ResolveContest::ordinary`] etc.
619   ///
620   /// Fails, if this setting ought to have been specified.
621   /// The caller will add a key name to the error.
622   fn default_for_ordinary() -> Result<Self, AE> {
623     Err(anyhow!("setting must be specified"))
624   }
625
626   /// Placeholder (infalliable)
627   ///
628   /// Used (sometimes) for lookups with
629   /// [`ResolveContest::client`],
630   /// [`server`](`ResolveContest::server`) and
631   /// [`computed`](`ResolveContest::server`).
632   ///
633   /// Ie, when the value need not be specified because
634   /// it may not be applicable, or could be computed.
635   ///
636   /// We could use `Default::default` but
637   /// not all the types we want to use implement that.
638   fn unspecified() -> Self;
639 }
640
641 impl Parseable for Duration {
642   #[throws(AE)]
643   fn parse(s: Option<&str>) -> Duration {
644     // todo: would be nice to parse with humantime maybe
645     Duration::from_secs( s.value()?.parse()? )
646   }
647   fn unspecified() -> Duration { Duration::ZERO }
648 }
649 macro_rules! parseable_from_str { ($t:ty, $def:expr) => {
650   impl Parseable for $t {
651     #[throws(AE)]
652     fn parse(s: Option<&str>) -> $t { s.value()?.parse()? }
653     #[throws(AE)]
654     fn default_for_ordinary() -> Self { Parseable::unspecified() }
655     fn unspecified() -> Self { $def }
656   }
657 } }
658 parseable_from_str!{u16, default() }
659 parseable_from_str!{u32, default() }
660 parseable_from_str!{String, default() }
661 parseable_from_str!{IpNet, default() }
662 parseable_from_str!{IpAddr, Ipv4Addr::UNSPECIFIED.into() }
663
664 parseable_from_str!{
665   Url,
666   "hippotat-unspecified:".parse()
667     .expect("failed to parse `hippotat-unspecified:` as a url")
668 }
669
670 impl<T:Parseable> Parseable for Vec<T> {
671   #[throws(AE)]
672   fn parse(s: Option<&str>) -> Vec<T> {
673     s.value()?
674       .split_ascii_whitespace()
675       .map(|s| Parseable::parse(Some(s)))
676       .collect::<Result<Vec<_>,_>>()?
677   }
678   #[throws(AE)]
679   fn default_for_ordinary() -> Self { Parseable::unspecified() }
680   fn unspecified() -> Self { default() }
681 }
682
683
684 #[derive(Debug,Copy,Clone,Eq,PartialEq)]
685 enum SectionKindList {
686   PerClient,
687   Limited,
688   Limits,
689   Global,
690   ServerName,
691   None,
692 }
693 use SectionKindList as SKL;
694
695 impl SectionName {
696   fn special_server_section() -> Self { SN::Server(ServerName(
697     SPECIAL_SERVER_SECTION.into()
698   )) }
699 }
700
701 impl SectionKindList {
702   fn contains(self, s: &SectionName, end: LinkEnd) -> bool {
703     match (self, end) {
704       (SKL::PerClient,_) |
705       (SKL::Global, LinkEnd::Client) => matches!(s, SN::Link(_)
706                                                   | SN::Client(_)
707                                                   | SN::Server(_)
708                                                   | SN::Common),
709
710       (SKL::Limits,_)     => matches!(s, SN::ServerLimit(_)
711                                        | SN::GlobalLimit),
712
713       (SKL::Global, LinkEnd::Server) => matches!(s, SN::Common
714                                                   | SN::Server(_)),
715
716       (SKL::Limited,_)    => SKL::PerClient.contains(s, end)
717                            | SKL::Limits   .contains(s, end),
718
719       (SKL::ServerName,_) => matches!(s, SN::Common)
720                            | matches!(s, SN::Server(ServerName(name))
721                                          if name == SPECIAL_SERVER_SECTION),
722       (SKL::None,_)       => false,
723     }
724   }
725 }
726
727 impl Aggregate {
728   fn lookup_raw<'a,'s,S>(&'a self, key: &'static str, sections: S)
729                        -> Option<RawValRef<'a,'a,'s>>
730   where S: Iterator<Item=&'s SectionName>
731   {
732     for section in sections {
733       if let Some(val) = self.sections
734         .get(section)
735         .and_then(|s: &ini::Section| s.values.get(key))
736       {
737         return Some(RawValRef {
738           raw: Some(&val.val),
739           loc: &val.loc,
740           section, key,
741         })
742       }
743     }
744     None
745   }
746
747   #[throws(AE)]
748   pub fn establish_server_name(&self) -> ServerName {
749     let key = "server";
750     let raw = match self.lookup_raw(
751       key,
752       [ &SectionName::Common, &SN::special_server_section() ].iter().cloned()
753     ) {
754       Some(raw) => raw.try_map(|os| os.value())?,
755       None => SPECIAL_SERVER_SECTION,
756     };
757     ServerName(raw.into())
758   }
759 }
760
761 impl<'c> ResolveContext<'c> {
762   fn first_of_raw(&'c self, key: &'static str, sections: SectionKindList)
763                   -> Option<RawValRef<'c,'c,'c>> {
764     self.agg.lookup_raw(
765       key,
766       self.all_sections.iter()
767         .filter(|s| sections.contains(s, self.end))
768     )
769   }
770
771   #[throws(AE)]
772   fn first_of<T>(&self, key: &'static str, sections: SectionKindList)
773                  -> Option<T>
774   where T: Parseable
775   {
776     match self.first_of_raw(key, sections) {
777       None => None,
778       Some(raw) => Some(raw.try_map(Parseable::parse)?),
779     }
780   }
781
782   #[throws(AE)]
783   pub fn ordinary<T>(&self, key: &'static str, skl: SKL) -> T
784   where T: Parseable
785   {
786     match self.first_of(key, skl)? {
787       Some(y) => y,
788       None => Parseable::default_for_ordinary()
789         .with_context(|| key.to_string())?,
790     }
791   }
792
793   #[throws(AE)]
794   pub fn limited<T>(&self, key: &'static str, skl: SKL) -> T
795   where T: Parseable + Ord
796   {
797     assert_eq!(skl, SKL::Limited);
798     let val = self.ordinary(key, SKL::PerClient)?;
799     if let Some(limit) = self.first_of(key, SKL::Limits)? {
800       min(val, limit)
801     } else {
802       val
803     }
804   }
805
806   #[throws(AE)]
807   pub fn client<T>(&self, key: &'static str, skl: SKL) -> T
808   where T: Parseable {
809     match self.end {
810       LinkEnd::Client => self.ordinary(key, skl)?,
811       LinkEnd::Server => Parseable::unspecified(),
812     }
813   }
814   #[throws(AE)]
815   pub fn server<T>(&self, key: &'static str, skl: SKL) -> T
816   where T: Parseable {
817     match self.end {
818       LinkEnd::Server => self.ordinary(key, skl)?,
819       LinkEnd::Client => Parseable::unspecified(),
820     }
821   }
822
823   #[throws(AE)]
824   pub fn computed<T>(&self, _key: &'static str, skl: SKL) -> T
825   where T: Parseable
826   {
827     assert_eq!(skl, SKL::None);
828     Parseable::unspecified()
829   }
830
831   #[throws(AE)]
832   pub fn special_ipif(&self, key: &'static str, skl: SKL) -> String {
833     assert_eq!(skl, SKL::PerClient); // we tolerate it in per-client sections
834     match self.end {
835       LinkEnd::Client => self.ordinary(key, SKL::PerClient)?,
836       LinkEnd::Server => self.ordinary(key, SKL::Global)?,
837     }
838   }
839
840   #[throws(AE)]
841   pub fn special_link(&self, _key: &'static str, skl: SKL) -> LinkName {
842     assert_eq!(skl, SKL::None);
843     self.link.clone()
844   }
845
846   #[throws(AE)]
847   pub fn special_max_up(&self, key: &'static str, skl: SKL) -> u32 {
848     assert_eq!(skl, SKL::Limited);
849     match self.end {
850       LinkEnd::Client => self.ordinary(key, SKL::Limited)?,
851       LinkEnd::Server => self.ordinary(key, SKL::Limits)?,
852     }
853   }
854 }
855
856 impl InstanceConfig {
857   #[throws(AE)]
858   fn complete(&mut self, end: LinkEnd) {
859     let mut vhosts = self.vnetwork.iter()
860       .map(|n| n.hosts()).flatten()
861       .filter({ let vaddr = self.vaddr; move |v| v != &vaddr });
862
863     if self.vaddr.is_unspecified() {
864       self.vaddr = vhosts.next().ok_or_else(
865         || anyhow!("vnetwork too small to generate vaddrr")
866       )?;
867     }
868     if self.vrelay.is_unspecified() {
869       self.vrelay = vhosts.next().ok_or_else(
870         || anyhow!("vnetwork too small to generate vrelay")
871       )?;
872     }
873
874     let check_batch = {
875       let mtu = self.mtu;
876       move |max_batch, key| {
877         if max_batch/2 < mtu {
878           throw!(anyhow!("max batch {:?} ({}) must be >= 2 x mtu ({}) \
879                           (to allow for SLIP ESC-encoding)",
880                          key, max_batch, mtu))
881         }
882         Ok::<_,AE>(())
883       }
884     };
885
886     match end {
887       LinkEnd::Client => {
888         if self.url == Url::unspecified() {
889           let addr = self.addrs.get(0).ok_or_else(
890             || anyhow!("client needs addrs or url set")
891           )?;
892           self.url = format!(
893             "http://{}{}/",
894             match addr {
895               IpAddr::V4(a) => format!("{}", a),
896               IpAddr::V6(a) => format!("[{}]", a),
897             },
898             match self.port {
899               80 => format!(""),
900               p => format!(":{}", p),
901             })
902             .parse().unwrap()
903         }
904
905         self.effective_http_timeout = {
906           let a = self.http_timeout;
907           let b = self.http_timeout_grace;
908           a.checked_add(b).ok_or_else(
909             || anyhow!("calculate effective http timeout ({:?} + {:?})", a, b)
910           )?
911         };
912
913         {
914           let t = self.target_requests_outstanding;
915           let m = self.max_requests_outstanding;
916           if t > m { throw!(anyhow!(
917             "target_requests_outstanding ({}) > max_requests_outstanding ({})",
918             t, m
919           )) }
920         }
921
922         check_batch(self.max_batch_up, "max_batch_up")?;
923       },
924
925       LinkEnd::Server => {
926         if self.addrs.is_empty() {
927           throw!(anyhow!("missing 'addrs' setting"))
928         }
929         check_batch(self.max_batch_down, "max_batch_down")?;
930       },
931     }
932
933     #[throws(AE)]
934     fn subst(var: &mut String,
935              kv: &mut dyn Iterator<Item=(&'static str, &dyn Display)>
936     ) {
937       let substs = kv
938         .map(|(k,v)| (k.to_string(), v.to_string()))
939         .collect::<HashMap<String, String>>();
940       let bad = parking_lot::Mutex::new(vec![]);
941       *var = regex_replace_all!(
942         r#"%(?:%|\((\w+)\)s|\{(\w+)\}|.)"#,
943         var,
944         |whole, k1, k2| (|| Ok::<_,String>({
945           if whole == "%%" { "%" }
946           else if let Some(&k) = [k1,k2].iter().find(|&&s| s != "") {
947             substs.get(k).ok_or_else(
948               || format!("unknown key %({})s", k)
949             )?
950           } else {
951             throw!(format!("bad percent escape {:?}", &whole));
952           }
953         }))().unwrap_or_else(|e| { bad.lock().push(e); "" })
954       ).into_owned();
955       let bad = bad.into_inner();
956       if ! bad.is_empty() {
957         throw!(anyhow!("substitution failed: {}", bad.iter().format("; ")));
958       }
959     }
960
961     {
962       use LinkEnd::*;
963       type DD<'d> = &'d dyn Display;
964       fn dv<T:Display>(v: &[T]) -> String {
965         format!("{}", v.iter().format(" "))
966       }
967       let mut ipif = mem::take(&mut self.ipif); // lets us borrow all of self
968       let s = &self; // just for abbreviation, below
969       let vnetwork = dv(&s.vnetwork);
970       let vroutes  = dv(&s.vroutes);
971
972       let keys = &["local",       "peer",    "rnets",   "ifname"];
973       let values = match end {
974  Server => [&s.vaddr as DD      , &s.vrelay, &vnetwork, &s.ifname_server],
975  Client => [&s.link.client as DD, &s.vaddr,  &vroutes,  &s.ifname_client],
976       };
977       let always = [
978         ( "mtu",     &s.mtu as DD ),
979       ];
980
981       subst(
982         &mut ipif,
983         &mut keys.iter().cloned()
984           .zip_eq(values)
985           .chain(always.iter().cloned()),
986       ).context("ipif")?;
987       self.ipif = ipif;
988     }
989   }
990 }
991
992 trait ResolveGlobal<'i> where Self: 'i {
993   fn resolve<I>(it: I) -> Self
994   where I: Iterator<Item=&'i Self>;
995 }
996 impl<'i,T> ResolveGlobal<'i> for T where T: Eq + Clone + Debug + 'i {
997   fn resolve<I>(mut it: I) -> Self
998   where I: Iterator<Item=&'i Self>
999   {
1000     let first = it.next().expect("empty instances no global!");
1001     for x in it { assert_eq!(x, first); }
1002     first.clone()
1003   }
1004 }
1005
1006 #[throws(AE)]
1007 pub fn read(opts: &CommonOpts, end: LinkEnd)
1008             -> (Option<ServerName>, Vec<InstanceConfig>)
1009 {
1010   let agg = (||{
1011     let mut agg = Aggregate::new(
1012       end,
1013       InstanceConfig::FIELDS.iter().cloned().collect(),
1014     );
1015
1016     agg.read_string(DEFAULT_CONFIG.into(),
1017                     "<build-in defaults>".as_ref())
1018       .expect("builtin configuration is broken");
1019
1020     agg.read_toplevel(&opts.config)?;
1021     for extra in &opts.extra_config {
1022       agg.read_extra(extra).context("extra config")?;
1023     }
1024
1025     //eprintln!("GOT {:#?}", agg);
1026
1027     Ok::<_,AE>(agg)
1028   })().context("read configuration")?;
1029
1030   let server_name = match end {
1031     LinkEnd::Server => Some(agg.establish_server_name()?),
1032     LinkEnd::Client => None,
1033   };
1034
1035   let instances = agg.instances(server_name.as_ref());
1036   let mut ics = vec![];
1037   //dbg!(&instances);
1038
1039   for link in instances {
1040     let rctx = ResolveContext {
1041       agg: &agg,
1042       link: &link,
1043       end,
1044       all_sections: vec![
1045         SN::Link(link.clone()),
1046         SN::Client(link.client.clone()),
1047         SN::Server(link.server.clone()),
1048         SN::Common,
1049         SN::ServerLimit(link.server.clone()),
1050         SN::GlobalLimit,
1051       ],
1052     };
1053
1054     if rctx.first_of_raw("secret", SKL::PerClient).is_none() { continue }
1055
1056     let mut ic = InstanceConfig::resolve_instance(&rctx)
1057       .with_context(|| format!("resolve config for {}", &link))?;
1058
1059     ic.complete(end)
1060       .with_context(|| format!("complete config for {}", &link))?;
1061
1062     ics.push(ic);
1063   }
1064
1065   (server_name, ics)
1066 }
1067
1068 pub async fn startup<F,T,G,GFut,U>(progname: &str, end: LinkEnd,
1069                     opts: &CommonOpts, logopts: &LogOpts,
1070                     f: F, g: G) -> U
1071 where F: FnOnce(Option<ServerName>, &[InstanceConfig]) -> Result<T,AE>,
1072       G: FnOnce(T, Vec<InstanceConfig>) -> GFut,
1073       GFut: Future<Output=Result<U,AE>>,
1074 {
1075   async {
1076     dedup_eyre_setup()?;
1077     let (server_name, ics) = config::read(opts, end)?;
1078
1079     let t = f(server_name, &ics)?;
1080     if ics.is_empty() { throw!(anyhow!("no associations, quitting")); }
1081
1082     logopts.log_init()?;
1083     let u = g(t, ics).await?;
1084
1085     Ok::<_,AE>(u)
1086   }.await.unwrap_or_else(|e| {
1087     eprintln!("{}: startup error: {}", progname, &e);
1088     process::exit(8);
1089   })
1090 }