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