chiark / gitweb /
config: Refactor substutiton
[hippotat.git] / src / config.rs
1 // Copyright 2021 Ian Jackson and contributors to Hippotat
2 // SPDX-License-Identifier: AGPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 use configparser::ini::Ini;
8
9 #[derive(hippotat_macros::ResolveConfig)]
10 #[derive(Debug,Clone)]
11 pub struct InstanceConfig {
12   // Exceptional settings
13   #[special(special_link, SKL::ServerName)]   pub link:   LinkName,
14   pub                                             secret: Secret,
15   #[special(special_ipif, SKL::Ordinary)]     pub ipif:   String,
16
17   // Capped settings:
18   #[limited]    pub max_batch_down:               u32,
19   #[limited]    pub max_queue_time:               Duration,
20   #[limited]    pub http_timeout:                 Duration,
21   #[limited]    pub target_requests_outstanding:  u32,
22
23   // Ordinary settings:
24   pub addrs:                        Vec<IpAddr>,
25   pub vnetwork:                     Vec<IpNet>,
26   pub vaddr:                        IpAddr,
27   pub vrelay:                       IpAddr,
28   pub port:                         u16,
29   pub mtu:                          u32,
30   pub ifname_server:                String,
31   pub ifname_client:                String,
32
33   // Ordinary settings, used by server only:
34   #[server]  pub max_clock_skew:               Duration,
35
36   // Ordinary settings, used by client only:
37   #[client]  pub http_timeout_grace:           Duration,
38   #[client]  pub max_requests_outstanding:     u32,
39   #[client]  pub max_batch_up:                 u32,
40   #[client]  pub http_retry:                   Duration,
41   #[client]  pub url:                          Uri,
42   #[client]  pub vroutes:                      Vec<IpNet>,
43 }
44
45 static DEFAULT_CONFIG: &str = r#"
46 [COMMON]
47 max_batch_down = 65536
48 max_queue_time = 10
49 target_requests_outstanding = 3
50 http_timeout = 30
51 http_timeout_grace = 5
52 max_requests_outstanding = 6
53 max_batch_up = 4000
54 http_retry = 5
55 port = 80
56 vroutes = ''
57 ifname_client = hippo%%d
58 ifname_server = shippo%%d
59 max_clock_skew = 300
60
61 ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s '%(rnets)s'
62
63 mtu = 1500
64
65 vnetwork = 172.24.230.192
66
67 [LIMIT]
68 max_batch_down = 262144
69 max_queue_time = 121
70 http_timeout = 121
71 target_requests_outstanding = 10
72 "#;
73
74 #[derive(StructOpt,Debug)]
75 pub struct Opts {
76   /// Top-level config file or directory
77   ///
78   /// Look for `main.cfg`, `config.d` and `secrets.d` here.
79   ///
80   /// Or if this is a file, just read that file.
81   #[structopt(long, default_value="/etc/hippotat")]
82   pub config: PathBuf,
83   
84   /// Additional config files or dirs, which can override the others
85   #[structopt(long, multiple=true, number_of_values=1)]
86   pub extra_config: Vec<PathBuf>,
87 }
88
89 #[ext]
90 impl<'s> Option<&'s str> {
91   #[throws(AE)]
92   fn value(self) -> &'s str {
93     self.ok_or_else(|| anyhow!("value needed"))?
94   }
95 }
96
97 #[derive(Clone)]
98 pub struct Secret(pub String);
99 impl Parseable for Secret {
100   #[throws(AE)]
101   fn parse(s: Option<&str>) -> Self {
102     let s = s.value()?;
103     if s.is_empty() { throw!(anyhow!("secret value cannot be empty")) }
104     Secret(s.into())
105   }
106   #[throws(AE)]
107   fn default() -> Self { Secret(default()) }
108 }
109 impl Debug for Secret {
110   #[throws(fmt::Error)]
111   fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Secret(***)")? }
112 }
113
114 #[derive(Debug,Clone,Hash,Eq,PartialEq)]
115 pub enum SectionName {
116   Link(LinkName),
117   Client(ClientName),
118   Server(ServerName), // includes SERVER, which is slightly special
119   ServerLimit(ServerName),
120   GlobalLimit,
121   Common,
122 }
123 pub use SectionName as SN;
124
125 #[derive(Debug,Clone)]
126 struct RawVal { raw: Option<String>, loc: Arc<PathBuf> }
127 type SectionMap = HashMap<String, RawVal>;
128
129 #[derive(Debug)]
130 struct RawValRef<'v,'l,'s> {
131   raw: Option<&'v str>,
132   key: &'static str,
133   loc: &'l Path,
134   section: &'s SectionName,
135 }
136
137 impl<'v> RawValRef<'v,'_,'_> {
138   #[throws(AE)]
139   fn try_map<F,T>(&self, f: F) -> T
140   where F: FnOnce(Option<&'v str>) -> Result<T, AE> {
141     f(self.raw)
142       .with_context(|| format!(r#"file {:?}, section {}, key "{}""#,
143                                self.loc, self.section, self.key))?
144   }
145 }
146
147 pub struct Config {
148   pub opts: Opts,
149 }
150
151 static OUTSIDE_SECTION: &str = "[";
152 static SPECIAL_SERVER_SECTION: &str = "SERVER";
153
154 #[derive(Default,Debug)]
155 struct Aggregate {
156   keys_allowed: HashMap<&'static str, SectionKindList>,
157   sections: HashMap<SectionName, SectionMap>,
158 }
159
160 type OkAnyway<'f,A> = &'f dyn Fn(ErrorKind) -> Option<A>;
161 #[ext]
162 impl<'f,A> OkAnyway<'f,A> {
163   fn ok<T>(self, r: &Result<T, io::Error>) -> Option<A> {
164     let e = r.as_ref().err()?;
165     let k = e.kind();
166     let a = self(k)?;
167     Some(a)
168   }
169 }
170
171 impl FromStr for SectionName {
172   type Err = AE;
173   #[throws(AE)]
174   fn from_str(s: &str) -> Self {
175     match s {
176       "COMMON" => return SN::Common,
177       "LIMIT" => return SN::GlobalLimit,
178       _ => { }
179     };
180     if let Ok(n@ ServerName(_)) = s.parse() { return SN::Server(n) }
181     if let Ok(n@ ClientName(_)) = s.parse() { return SN::Client(n) }
182     let (server, client) = s.split_ascii_whitespace().collect_tuple()
183       .ok_or_else(|| anyhow!(
184         "bad section name {:?} \
185          (must be COMMON, DEFAULT, <server>, <client>, or <server> <client>",
186         s
187       ))?;
188     let server = server.parse().context("server name in link section name")?;
189     if client == "LIMIT" { return SN::ServerLimit(server) }
190     let client = client.parse().context("client name in link section name")?;
191     SN::Link(LinkName { server, client })
192   }
193 }
194 impl Display for InstanceConfig {
195   #[throws(fmt::Error)]
196   fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.link, f)? }
197 }
198
199 impl Display for SectionName {
200   #[throws(fmt::Error)]
201   fn fmt(&self, f: &mut fmt::Formatter) {
202     match self {
203       SN::Link  (ref l)      => Display::fmt(l, f)?,
204       SN::Client(ref c)      => write!(f, "[{}]"       , c)?,
205       SN::Server(ref s)      => write!(f, "[{}]"       , s)?,
206       SN::ServerLimit(ref s) => write!(f, "[{} LIMIT] ", s)?,
207       SN::GlobalLimit        => write!(f, "[LIMIT]"       )?,
208       SN::Common             => write!(f, "[COMMON]"      )?,
209     }
210   }
211 }
212
213 impl Aggregate {
214   #[throws(AE)] // AE does not include path
215   fn read_file<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
216   {
217     let f = fs::File::open(path);
218     if let Some(anyway) = anyway.ok(&f) { return Some(anyway) }
219     let mut f = f.context("open")?;
220
221     let mut s = String::new();
222     let y = f.read_to_string(&mut s);
223     if let Some(anyway) = anyway.ok(&y) { return Some(anyway) }
224     y.context("read")?;
225
226     self.read_string(s, path)?;
227     None
228   }
229
230   #[throws(AE)] // AE does not include path
231   fn read_string(&mut self, s: String, path_for_loc: &Path) {
232     let mut ini = Ini::new_cs();
233     ini.set_default_section(OUTSIDE_SECTION);
234     ini.read(s).map_err(|e| anyhow!("{}", e)).context("parse as INI")?;
235     let map = mem::take(ini.get_mut_map());
236     if map.get(OUTSIDE_SECTION).is_some() {
237       throw!(anyhow!("INI file contains settings outside a section"));
238     }
239
240     let loc = Arc::new(path_for_loc.to_owned());
241
242     for (sn, vars) in map {
243       let sn = sn.parse().dcontext(&sn)?;
244
245       for key in vars.keys() {
246         let skl = self.keys_allowed.get(key.as_str()).ok_or_else(
247           || anyhow!("unknown configuration key {:?}", key)
248         )?;
249         if ! skl.contains(&sn) {
250           throw!(anyhow!("configuration key {:?} not applicable \
251                           in this kind of section {:?}", key, &sn))
252         }
253       }
254
255       let ent = self.sections.entry(sn).or_default();
256       for (key, raw) in vars {
257         let raw = match raw {
258           Some(raw) if raw.starts_with('\'') || raw.starts_with('"') => Some(
259             (||{
260               if raw.contains('\\') {
261                 throw!(
262                   anyhow!("quoted value contains backslash, not supported")
263                 );
264               }
265               let unq = raw[1..].strip_suffix(&raw[0..1])
266                 .ok_or_else(
267                   || anyhow!("mismatched quotes around quoted value")
268                 )?
269                 .to_owned();
270               Ok::<_,AE>(unq)
271             })()
272               .with_context(|| format!("key {:?}", key))
273               .dcontext(path_for_loc)?
274           ),
275           x => x,
276         };
277         let key = key.replace('-',"_");
278         ent.insert(key, RawVal { raw, loc: loc.clone() });
279       }
280     }
281   }
282
283   #[throws(AE)] // AE includes path
284   fn read_dir_d<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
285   {
286     let dir = fs::read_dir(path);
287     if let Some(anyway) = anyway.ok(&dir) { return Some(anyway) }
288     let dir = dir.context("open directory").dcontext(path)?;
289     for ent in dir {
290       let ent = ent.context("read directory").dcontext(path)?;
291       let leaf = ent.file_name();
292       let leaf = leaf.to_str();
293       let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8?
294       if leaf.len() == 0 { continue }
295       if ! leaf.chars().all(
296         |c| c=='-' || c=='_' || c.is_ascii_alphanumeric()
297       ) { continue }
298
299       // OK we want this one
300       let ent = ent.path();
301       self.read_file(&ent, &|_| None::<Void>).dcontext(&ent)?;
302     }
303     None
304   }
305
306   #[throws(AE)] // AE includes everything
307   fn read_toplevel(&mut self, toplevel: &Path) {
308     enum Anyway { None, Dir }
309     match self.read_file(toplevel, &|k| match k {
310       EK::NotFound => Some(Anyway::None),
311       EK::IsADirectory => Some(Anyway::Dir),
312       _ => None,
313     })
314       .dcontext(toplevel).context("top-level config directory (or file)")?
315     {
316       None | Some(Anyway::None) => { },
317
318       Some(Anyway::Dir) => {
319         struct AnywayNone;
320         let anyway_none = |k| match k {
321           EK::NotFound => Some(AnywayNone),
322           _ => None,
323         };
324
325         let mk = |leaf: &str| {
326           [ toplevel, &PathBuf::from(leaf) ]
327             .iter().collect::<PathBuf>()
328         };
329
330         for &(try_main, desc) in &[
331           ("main.cfg", "main config file"),
332           ("master.cfg", "obsolete-named main config file"),
333         ] {
334           let main = mk(try_main);
335
336           match self.read_file(&main, &anyway_none)
337             .dcontext(main).context(desc)?
338           {
339             None => break,
340             Some(AnywayNone) => { },
341           }
342         }
343
344         for &(try_dir, desc) in &[
345           ("config.d", "per-link config directory"),
346           ("secrets.d", "per-link secrets directory"),
347         ] {
348           let dir = mk(try_dir);
349           match self.read_dir_d(&dir, &anyway_none).context(desc)? {
350             None => { },
351             Some(AnywayNone) => { },
352           }
353         }
354       }
355     }
356   }
357
358   #[throws(AE)] // AE includes extra, but does that this is extra
359   fn read_extra(&mut self, extra: &Path) {
360     struct AnywayDir;
361
362     match self.read_file(extra, &|k| match k {
363       EK::IsADirectory => Some(AnywayDir),
364       _ => None,
365     })
366       .dcontext(extra)?
367     {
368       None => return,
369       Some(AnywayDir) => {
370         self.read_dir_d(extra, &|_| None::<Void>)?;
371       }
372     }
373
374   }
375 }
376
377 impl Aggregate {
378   fn instances(&self, only_server: Option<&ServerName>) -> BTreeSet<LinkName> {
379     let mut links:              BTreeSet<LinkName> = default();
380
381     let mut secrets_anyserver:  BTreeSet<&ClientName> = default();
382     let mut secrets_anyclient:  BTreeSet<&ServerName> = default();
383     let mut secret_global       = false;
384
385     let mut putative_servers   = BTreeSet::new();
386     let mut putative_clients   = BTreeSet::new();
387
388     let mut note_server = |s| {
389       if let Some(only) = only_server { if s != only { return false } }
390       putative_servers.insert(s);
391       true
392     };
393     let mut note_client = |c| {
394       putative_clients.insert(c);
395     };
396
397     for (section, vars) in &self.sections {
398       let has_secret = || vars.contains_key("secret");
399
400       match section {
401         SN::Link(l) => {
402           if ! note_server(&l.server) { continue }
403           note_client(&l.client);
404           if has_secret() { links.insert(l.clone()); }
405         },
406         SN::Server(ref s) => {
407           if ! note_server(s) { continue }
408           if has_secret() { secrets_anyclient.insert(s); }
409         },
410         SN::Client(ref c) => {
411           note_client(c);
412           if has_secret() { secrets_anyserver.insert(c); }
413         },
414         SN::Common => {
415           if has_secret() { secret_global = true; }
416         },
417         _ => { },
418       }
419     }
420
421     // Add links which are justified by blanket secrets
422     for (client, server) in iproduct!(
423       putative_clients.into_iter().filter(
424         |c| secret_global || secrets_anyserver.contains(c)
425       ),
426       putative_servers.iter().cloned().filter(
427         |s| secret_global || secrets_anyclient.contains(s)
428       )
429     ) {
430       links.insert(LinkName {
431         client: client.clone(),
432         server: server.clone(),
433       });
434     }
435
436     links
437   }
438 }
439
440 struct ResolveContext<'c> {
441   agg: &'c Aggregate,
442   link: &'c LinkName,
443   end: LinkEnd,
444   all_sections: Vec<SectionName>,
445 }
446
447 trait Parseable: Sized {
448   fn parse(s: Option<&str>) -> Result<Self, AE>;
449   fn default() -> Result<Self, AE> {
450     Err(anyhow!("setting must be specified"))
451   }
452   #[throws(AE)]
453   fn default_for_key(key: &str) -> Self {
454     Self::default().with_context(|| key.to_string())?
455   }
456 }
457
458 impl Parseable for Duration {
459   #[throws(AE)]
460   fn parse(s: Option<&str>) -> Duration {
461     // todo: would be nice to parse with humantime maybe
462     Duration::from_secs( s.value()?.parse()? )
463   }
464 }
465 macro_rules! parseable_from_str { ($t:ty $(, $def:expr)? ) => {
466   impl Parseable for $t {
467     #[throws(AE)]
468     fn parse(s: Option<&str>) -> $t { s.value()?.parse()? }
469     $( #[throws(AE)] fn default() -> Self { $def } )?
470   }
471 } }
472 parseable_from_str!{u16, default() }
473 parseable_from_str!{u32, default() }
474 parseable_from_str!{String, default() }
475 parseable_from_str!{IpNet, default() }
476 parseable_from_str!{IpAddr, Ipv4Addr::UNSPECIFIED.into() }
477 parseable_from_str!{Uri, default() }
478
479 impl<T:Parseable> Parseable for Vec<T> {
480   #[throws(AE)]
481   fn parse(s: Option<&str>) -> Vec<T> {
482     s.value()?
483       .split_ascii_whitespace()
484       .map(|s| Parseable::parse(Some(s)))
485       .collect::<Result<Vec<_>,_>>()?
486   }
487   #[throws(AE)]
488   fn default() -> Self { default() }
489 }
490
491
492 #[derive(Debug,Copy,Clone)]
493 enum SectionKindList {
494   Ordinary,
495   Limited,
496   Limits,
497   ClientAgnostic,
498   ServerName,
499 }
500 use SectionKindList as SKL;
501
502 impl SectionName {
503   fn special_server_section() -> Self { SN::Server(ServerName(
504     SPECIAL_SERVER_SECTION.into()
505   )) }
506 }
507
508 impl SectionKindList {
509   fn contains(self, s: &SectionName) -> bool {
510     match self {
511       SKL::Ordinary       => matches!(s, SN::Link(_)
512                                        | SN::Client(_)
513                                        | SN::Server(_)
514                                        | SN::Common),
515
516       SKL::Limits         => matches!(s, SN::ServerLimit(_)
517                                        | SN::GlobalLimit),
518
519       SKL::ClientAgnostic => matches!(s, SN::Common
520                                        | SN::Server(_)),
521
522       SKL::Limited        => SKL::Ordinary.contains(s)
523                            | SKL::Limits  .contains(s),
524
525       SKL::ServerName     => matches!(s, SN::Common)
526                            | matches!(s, SN::Server(ServerName(name))
527                                          if name == SPECIAL_SERVER_SECTION),
528     }
529   }
530 }
531
532 impl Aggregate {
533   fn lookup_raw<'a,'s,S>(&'a self, key: &'static str, sections: S)
534                        -> Option<RawValRef<'a,'a,'s>>
535   where S: Iterator<Item=&'s SectionName>
536   {
537     for section in sections {
538       if let Some(raw) = self.sections
539         .get(section)
540         .and_then(|vars: &SectionMap| vars.get(key))
541       {
542         return Some(RawValRef {
543           raw: raw.raw.as_deref(),
544           loc: &raw.loc,
545           section, key,
546         })
547       }
548     }
549     None
550   }
551
552   #[throws(AE)]
553   pub fn establish_server_name(&self) -> ServerName {
554     let key = "server";
555     let raw = match self.lookup_raw(
556       key,
557       [ &SectionName::Common, &SN::special_server_section() ].iter().cloned()
558     ) {
559       Some(raw) => raw.try_map(|os| os.value())?,
560       None => SPECIAL_SERVER_SECTION,
561     };
562     ServerName(raw.into())
563   }
564 }
565
566 impl<'c> ResolveContext<'c> {
567   fn first_of_raw(&'c self, key: &'static str, sections: SectionKindList)
568                   -> Option<RawValRef<'c,'c,'c>> {
569     self.agg.lookup_raw(
570       key,
571       self.all_sections.iter()
572         .filter(|s| sections.contains(s))
573     )
574   }
575
576   #[throws(AE)]
577   fn first_of<T>(&self, key: &'static str, sections: SectionKindList)
578                  -> Option<T>
579   where T: Parseable
580   {
581     match self.first_of_raw(key, sections) {
582       None => None,
583       Some(raw) => Some(raw.try_map(Parseable::parse)?),
584     }
585   }
586
587   #[throws(AE)]
588   pub fn ordinary<T>(&self, key: &'static str) -> T
589   where T: Parseable
590   {
591     match self.first_of(key, SKL::Ordinary)? {
592       Some(y) => y,
593       None => Parseable::default_for_key(key)?,
594     }
595   }
596
597   #[throws(AE)]
598   pub fn limited<T>(&self, key: &'static str) -> T
599   where T: Parseable + Ord
600   {
601     let val = self.ordinary(key)?;
602     if let Some(limit) = self.first_of(key, SKL::Limits)? {
603       min(val, limit)
604     } else {
605       val
606     }
607   }
608
609   #[throws(AE)]
610   pub fn client<T>(&self, key: &'static str) -> T
611   where T: Parseable + Default {
612     match self.end {
613       LinkEnd::Client => self.ordinary(key)?,
614       LinkEnd::Server => default(),
615     }
616   }
617   #[throws(AE)]
618   pub fn server<T>(&self, key: &'static str) -> T
619   where T: Parseable + Default {
620     match self.end {
621       LinkEnd::Server => self.ordinary(key)?,
622       LinkEnd::Client => default(),
623     }
624   }
625
626   #[throws(AE)]
627   pub fn special_ipif(&self, key: &'static str) -> String {
628     match self.end {
629       LinkEnd::Client => self.ordinary(key)?,
630       LinkEnd::Server => {
631         self.first_of(key, SKL::ClientAgnostic)?
632           .unwrap_or_default()
633       },
634     }
635   }
636
637   #[throws(AE)]
638   pub fn special_link(&self, _key: &'static str) -> LinkName {
639     self.link.clone()
640   }
641 }
642
643 impl InstanceConfig {
644   #[throws(AE)]
645   fn complete(&mut self, end: LinkEnd) {
646     let mut vhosts = self.vnetwork.iter()
647       .map(|n| n.hosts()).flatten()
648       .filter({ let vaddr = self.vaddr; move |v| v != &vaddr });
649
650     if self.vaddr.is_unspecified() {
651       self.vaddr = vhosts.next().ok_or_else(
652         || anyhow!("vnetwork too small to generate vaddrr")
653       )?;
654     }
655     if self.vrelay.is_unspecified() {
656       self.vrelay = vhosts.next().ok_or_else(
657         || anyhow!("vnetwork too small to generate vrelay")
658       )?;
659     }
660
661     match end {
662       LinkEnd::Client => {
663         if &self.url == &default::<Uri>() {
664           let addr = self.addrs.get(0).ok_or_else(
665             || anyhow!("client needs addrs or url set")
666           )?;
667           self.url = format!(
668             "http://{}{}/",
669             match addr {
670               IpAddr::V4(a) => format!("{}", a),
671               IpAddr::V6(a) => format!("[{}]", a),
672             },
673             match self.port {
674               80 => format!(""),
675               p => format!(":{}", p),
676             })
677             .parse().unwrap()
678         }
679       },
680
681       LinkEnd::Server => {
682         if self.addrs.is_empty() {
683           throw!(anyhow!("missing 'addrs' setting"))
684         }
685       },
686     }
687
688     #[throws(AE)]
689     fn subst(var: &mut String,
690              kv: &mut dyn Iterator<Item=(&'static str, &dyn Display)>
691     ) {
692       let substs = kv
693         .map(|(k,v)| (k.to_string(), v.to_string()))
694         .collect::<HashMap<String, String>>();
695       let bad = parking_lot::Mutex::new(vec![]);
696       *var = regex_replace_all!(
697         r#"%(?:%|\((\w+)\)s|.)"#,
698         &var,
699         |whole, k| (|| Ok::<_,String>({
700           if whole == "%%" { "%" }
701           else if k != "" {
702             substs.get(k).ok_or_else(
703               || format!("unknown key %({})s", k)
704             )?
705           } else {
706             throw!(format!("bad percent escape {:?}", &whole));
707           }
708         }))().unwrap_or_else(|e| { bad.lock().push(e); "" })
709       ).into_owned();
710       let bad = bad.into_inner();
711       if ! bad.is_empty() {
712         throw!(anyhow!("substitution failed: {}", bad.iter().format("; ")));
713       }
714     }
715
716     let ifname = match match end {
717       LinkEnd::Client => (&mut self.ifname_client, "ifname_client"),
718       LinkEnd::Server => (&mut self.ifname_server, "ifname_server"),
719     } { (var,name) => {
720       subst(var, &mut iter::empty()).context(name).context("interface name")?;
721       var
722     } };
723
724     {
725       use LinkEnd::*;
726       type DD<'d> = &'d dyn Display;
727       fn dv<T:Display>(v: &[T]) -> String {
728         format!("{}", v.iter().format(" "))
729       }
730       let mut ipif = mem::take(&mut self.ipif); // lets us borrow all of self
731       let s = &self; // just for abbreviation, below
732       let vnetwork = dv(&s.vnetwork);
733       let vroutes  = dv(&s.vroutes);
734
735       let keys = &["local",       "peer",    "rnets",   "ifname"];
736       let values = match end {
737  Server => [&s.vaddr as DD      , &s.vrelay, &vnetwork, &s.ifname_server],
738  Client => [&s.link.client as DD, &s.vaddr,  &vroutes,  &s.ifname_client],
739       };
740       let always = [
741         ( "mtu",     &s.mtu as DD ),
742       ];
743
744       subst(
745         &mut ipif,
746         &mut keys.iter().cloned()
747           .zip_eq(values)
748           .chain(always.iter().cloned()),
749       ).context("ipif")?;
750       self.ipif = ipif;
751     }
752   }
753 }
754
755 #[throws(AE)]
756 pub fn read(end: LinkEnd) -> Vec<InstanceConfig> {
757   let opts = config::Opts::from_args();
758
759   let agg = (||{
760     let mut agg = Aggregate::default();
761     agg.keys_allowed.extend(
762       InstanceConfig::FIELDS.iter().cloned()
763     );
764
765     agg.read_string(DEFAULT_CONFIG.into(),
766                     "<build-in defaults>".as_ref()).unwrap();
767
768     agg.read_toplevel(&opts.config)?;
769     for extra in &opts.extra_config {
770       agg.read_extra(extra).context("extra config")?;
771     }
772
773     //eprintln!("GOT {:#?}", agg);
774
775     Ok::<_,AE>(agg)
776   })().context("read configuration")?;
777
778   let server_name = match end {
779     LinkEnd::Server => Some(agg.establish_server_name()?),
780     LinkEnd::Client => None,
781   };
782
783   let instances = agg.instances(server_name.as_ref());
784   let mut ics = vec![];
785
786   for link in instances {
787     let rctx = ResolveContext {
788       agg: &agg,
789       link: &link,
790       end,
791       all_sections: vec![
792         SN::Link(link.clone()),
793         SN::Client(link.client.clone()),
794         SN::Server(link.server.clone()),
795         SN::Common,
796         SN::ServerLimit(link.server.clone()),
797         SN::GlobalLimit,
798       ],
799     };
800
801     let mut ic = InstanceConfig::resolve_instance(&rctx)
802       .with_context(|| format!("resolve config for {}", &link))?;
803
804     ic.complete(end)
805       .with_context(|| format!("complete config for {}", &link))?;
806
807     ics.push(ic);
808   }
809
810   ics
811 }