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