chiark / gitweb /
config: Copy defaults from python hippotat
[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 static DEFAULT_CONFIG: &str = r#"
10 [COMMON]
11 max_batch_down = 65536
12 max_queue_time = 10
13 target_requests_outstanding = 3
14 http_timeout = 30
15 http_timeout_grace = 5
16 max_requests_outstanding = 6
17 max_batch_up = 4000
18 http_retry = 5
19 port = 80
20 vroutes = ''
21 ifname_client = hippo%%d
22 ifname_server = shippo%%d
23 max_clock_skew = 300
24
25 #[server] or [<client>] overrides
26 ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s %(rnets)s
27
28 # relating to virtual network
29 mtu = 1500
30
31 # addrs = 127.0.0.1 ::1
32 # url
33
34 # relating to virtual network
35 vvnetwork = 172.24.230.192
36 # vnetwork = <prefix>/<len>
37 # vaddr    = <ipaddr>
38 # vrelay   = <ipaddr>
39
40
41 # [<client-ip4-or-ipv6-address>]
42 # secret = <secret>    # used by both, must match
43
44 [LIMIT]
45 max_batch_down = 262144
46 max_queue_time = 121
47 http_timeout = 121
48 target_requests_outstanding = 10
49 "#;
50
51 #[derive(StructOpt,Debug)]
52 pub struct Opts {
53   /// Top-level config file or directory
54   ///
55   /// Look for `main.cfg`, `config.d` and `secrets.d` here.
56   ///
57   /// Or if this is a file, just read that file.
58   #[structopt(long, default_value="/etc/hippotat")]
59   pub config: PathBuf,
60   
61   /// Additional config files or dirs, which can override the others
62   #[structopt(long, multiple=true, number_of_values=1)]
63   pub extra_config: Vec<PathBuf>,
64 }
65
66 #[ext]
67 impl<'s> Option<&'s str> {
68   #[throws(AE)]
69   fn value(self) -> &'s str {
70     self.ok_or_else(|| anyhow!("value needed"))?
71   }
72 }
73
74 #[derive(Clone)]
75 pub struct Secret(pub String);
76 impl Parseable for Secret {
77   #[throws(AE)]
78   fn parse(s: Option<&str>) -> Self {
79     let s = s.value()?;
80     if s.is_empty() { throw!(anyhow!("secret value cannot be empty")) }
81     Secret(s.into())
82   }
83   #[throws(AE)]
84   fn default() -> Self { Secret(default()) }
85 }
86 impl Debug for Secret {
87   #[throws(fmt::Error)]
88   fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "Secret(***)")? }
89 }
90
91 #[derive(hippotat_macros::ResolveConfig)]
92 #[derive(Debug,Clone)]
93 pub struct InstanceConfig {
94   // Exceptional settings
95   #[special(special_server, SKL::ServerName)] pub server: ServerName,
96   pub                                             secret: Secret,
97   #[special(special_ipif, SKL::Ordinary)]     pub ipif:   String,
98
99   // Capped settings:
100   #[limited]    pub max_batch_down:               u32,
101   #[limited]    pub max_queue_time:               Duration,
102   #[limited]    pub http_timeout:                 Duration,
103   #[limited]    pub target_requests_outstanding:  u32,
104
105   // Ordinary settings:
106   pub addrs:                        Vec<IpAddr>,
107   pub vnetwork:                     Vec<IpNet>,
108   pub vaddr:                        Vec<IpAddr>,
109   pub vrelay:                       IpAddr,
110   pub port:                         u16,
111   pub mtu:                          u32,
112   pub ifname_server:                String,
113   pub ifname_client:                String,
114
115   // Ordinary settings, used by server only:
116   #[server]  pub max_clock_skew:               Duration,
117
118   // Ordinary settings, used by client only:
119   #[client]  pub http_timeout_grace:           Duration,
120   #[client]  pub max_requests_outstanding:     u32,
121   #[client]  pub max_batch_up:                 u32,
122   #[client]  pub http_retry:                   Duration,
123   #[client]  pub url:                          Uri,
124   #[client]  pub vroutes:                      Vec<IpNet>,
125 }
126
127 #[derive(Debug,Clone,Hash,Eq,PartialEq)]
128 pub enum SectionName {
129   Link(LinkName),
130   Client(ClientName),
131   Server(ServerName), // includes SERVER, which is slightly special
132   ServerLimit(ServerName),
133   GlobalLimit,
134   Common,
135   Default,
136 }
137 pub use SectionName as SN;
138
139 #[derive(Debug,Clone)]
140 struct RawVal { raw: Option<String>, loc: Arc<PathBuf> }
141 type SectionMap = HashMap<String, RawVal>;
142
143 struct RawValRef<'v,'l,'s> {
144   raw: Option<&'v str>,
145   key: &'static str,
146   loc: &'l Path,
147   section: &'s SectionName,
148 }
149
150 impl<'v> RawValRef<'v,'_,'_> {
151   #[throws(AE)]
152   fn try_map<F,T>(&self, f: F) -> T
153   where F: FnOnce(Option<&'v str>) -> Result<T, AE> {
154     f(self.raw)
155       .with_context(|| format!(r#"file {:?}, section "{:?}", key "{}"#,
156                                self.loc, self.section, self.key))?
157   }
158 }
159
160 pub struct Config {
161   opts: Opts,
162 }
163
164 static OUTSIDE_SECTION: &str = "[";
165 static SPECIAL_SERVER_SECTION: &str = "SERVER";
166
167 #[derive(Default,Debug)]
168 struct Aggregate {
169   sections: HashMap<SectionName, SectionMap>,
170 }
171
172 type OkAnyway<'f,A> = &'f dyn Fn(ErrorKind) -> Option<A>;
173 #[ext]
174 impl<'f,A> OkAnyway<'f,A> {
175   fn ok<T>(self, r: &Result<T, io::Error>) -> Option<A> {
176     let e = r.as_ref().err()?;
177     let k = e.kind();
178     let a = self(k)?;
179     Some(a)
180   }
181 }
182
183 impl FromStr for SectionName {
184   type Err = AE;
185   #[throws(AE)]
186   fn from_str(s: &str) -> Self {
187     match s {
188       "COMMON" => return SN::Common,
189       "DEFAULT" => return SN::Default,
190       "LIMIT" => return SN::GlobalLimit,
191       _ => { }
192     };
193     if let Ok(n@ ServerName(_)) = s.parse() { return SN::Server(n) }
194     if let Ok(n@ ClientName(_)) = s.parse() { return SN::Client(n) }
195     let (server, client) = s.split_ascii_whitespace().collect_tuple()
196       .ok_or_else(|| anyhow!(
197         "bad section name {:?} \
198          (must be COMMON, DEFAULT, <server>, <client>, or <server> <client>",
199         s
200       ))?;
201     let server = server.parse().context("server name in link section name")?;
202     if client == "LIMIT" { return SN::ServerLimit(server) }
203     let client = client.parse().context("client name in link section name")?;
204     SN::Link(LinkName { server, client })
205   }
206 }
207
208 impl Aggregate {
209   #[throws(AE)] // AE does not include path
210   fn read_file<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
211   {
212     let f = fs::File::open(path);
213     if let Some(anyway) = anyway.ok(&f) { return Some(anyway) }
214     let mut f = f.context("open")?;
215
216     let mut s = String::new();
217     let y = f.read_to_string(&mut s);
218     if let Some(anyway) = anyway.ok(&y) { return Some(anyway) }
219     y.context("read")?;
220
221     self.read_string(s, path)?;
222     None
223   }
224
225   #[throws(AE)] // AE does not include path
226   fn read_string(&mut self, s: String, path_for_loc: &Path) {
227     let mut ini = Ini::new_cs();
228     ini.set_default_section(OUTSIDE_SECTION);
229     ini.read(s).map_err(|e| anyhow!("{}", e)).context("parse as INI")?;
230     let map = mem::take(ini.get_mut_map());
231     if map.get(OUTSIDE_SECTION).is_some() {
232       throw!(anyhow!("INI file contains settings outside a section"));
233     }
234
235     let loc = Arc::new(path_for_loc.to_owned());
236
237     for (sn, vars) in map {
238       dbg!( InstanceConfig::FIELDS );// check xxx vars are in fields
239
240       let sn = sn.parse().dcontext(&sn)?;
241         self.sections.entry(sn)
242         .or_default()
243         .extend(
244           vars.into_iter()
245             .map(|(k,raw)| {
246               (k.replace('-',"_"),
247                RawVal { raw, loc: loc.clone() })
248             })
249         );
250     }
251     
252   }
253
254   #[throws(AE)] // AE includes path
255   fn read_dir_d<A>(&mut self, path: &Path, anyway: OkAnyway<A>) -> Option<A>
256   {
257     let dir = fs::read_dir(path);
258     if let Some(anyway) = anyway.ok(&dir) { return Some(anyway) }
259     let dir = dir.context("open directory").dcontext(path)?;
260     for ent in dir {
261       let ent = ent.context("read directory").dcontext(path)?;
262       let leaf = ent.file_name();
263       let leaf = leaf.to_str();
264       let leaf = if let Some(leaf) = leaf { leaf } else { continue }; //utf8?
265       if leaf.len() == 0 { continue }
266       if ! leaf.chars().all(
267         |c| c=='-' || c=='_' || c.is_ascii_alphanumeric()
268       ) { continue }
269
270       // OK we want this one
271       let ent = ent.path();
272       self.read_file(&ent, &|_| None::<Void>).dcontext(&ent)?;
273     }
274     None
275   }
276
277   #[throws(AE)] // AE includes everything
278   fn read_toplevel(&mut self, toplevel: &Path) {
279     enum Anyway { None, Dir }
280     match self.read_file(toplevel, &|k| match k {
281       EK::NotFound => Some(Anyway::None),
282       EK::IsADirectory => Some(Anyway::Dir),
283       _ => None,
284     })
285       .dcontext(toplevel).context("top-level config directory (or file)")?
286     {
287       None | Some(Anyway::None) => { },
288
289       Some(Anyway::Dir) => {
290         struct AnywayNone;
291         let anyway_none = |k| match k {
292           EK::NotFound => Some(AnywayNone),
293           _ => None,
294         };
295
296         let mk = |leaf: &str| {
297           [ toplevel, &PathBuf::from(leaf) ]
298             .iter().collect::<PathBuf>()
299         };
300
301         for &(try_main, desc) in &[
302           ("main.cfg", "main config file"),
303           ("master.cfg", "obsolete-named main config file"),
304         ] {
305           let main = mk(try_main);
306
307           match self.read_file(&main, &anyway_none)
308             .dcontext(main).context(desc)?
309           {
310             None => break,
311             Some(AnywayNone) => { },
312           }
313         }
314
315         for &(try_dir, desc) in &[
316           ("config.d", "per-link config directory"),
317           ("secrets.d", "per-link secrets directory"),
318         ] {
319           let dir = mk(try_dir);
320           match self.read_dir_d(&dir, &anyway_none).context(desc)? {
321             None => { },
322             Some(AnywayNone) => { },
323           }
324         }
325       }
326     }
327   }
328
329   #[throws(AE)] // AE includes extra, but does that this is extra
330   fn read_extra(&mut self, extra: &Path) {
331     struct AnywayDir;
332
333     match self.read_file(extra, &|k| match k {
334       EK::IsADirectory => Some(AnywayDir),
335       _ => None,
336     })
337       .dcontext(extra)?
338     {
339       None => return,
340       Some(AnywayDir) => {
341         self.read_dir_d(extra, &|_| None::<Void>)?;
342       }
343     }
344
345   }
346 }
347
348 struct ResolveContext<'c> {
349   agg: &'c Aggregate,
350   link: &'c LinkName,
351   end: LinkEnd,
352   server_name: ServerName,
353   all_sections: Vec<SectionName>,
354 }
355
356 trait Parseable: Sized {
357   fn parse(s: Option<&str>) -> Result<Self, AE>;
358   fn default() -> Result<Self, AE> {
359     Err(anyhow!("setting must be specified"))
360   }
361   #[throws(AE)]
362   fn default_for_key(key: &str) -> Self {
363     Self::default().with_context(|| key.to_string())?
364   }
365 }
366
367 impl Parseable for Duration {
368   #[throws(AE)]
369   fn parse(s: Option<&str>) -> Duration {
370     // todo: would be nice to parse with humantime maybe
371     Duration::from_secs( s.value()?.parse()? )
372   }
373 }
374 macro_rules! parseable_from_str { ($t:ty $(, $def:expr)? ) => {
375   impl Parseable for $t {
376     #[throws(AE)]
377     fn parse(s: Option<&str>) -> $t { s.value()?.parse()? }
378     $( #[throws(AE)] fn default() -> Self { $def } )?
379   }
380 } }
381 parseable_from_str!{u16, default() }
382 parseable_from_str!{u32, default() }
383 parseable_from_str!{String, default() }
384 parseable_from_str!{IpNet, default() }
385 parseable_from_str!{IpAddr, Ipv4Addr::UNSPECIFIED.into() }
386 parseable_from_str!{Uri, default() }
387
388 impl<T:Parseable> Parseable for Vec<T> {
389   #[throws(AE)]
390   fn parse(s: Option<&str>) -> Vec<T> {
391     s.value()?
392       .split_ascii_whitespace()
393       .map(|s| Parseable::parse(Some(s)))
394       .collect::<Result<Vec<_>,_>>()?
395   }
396 }
397
398
399 #[derive(Debug,Copy,Clone)]
400 enum SectionKindList {
401   Ordinary,
402   Limited,
403   Limits,
404   ClientAgnostic,
405   ServerName,
406 }
407 use SectionKindList as SKL;
408
409 impl SectionName {
410   fn special_server_section() -> Self { SN::Server(ServerName(
411     SPECIAL_SERVER_SECTION.into()
412   )) }
413 }
414
415 impl SectionKindList {
416   fn contains(self, s: &SectionName) -> bool {
417     match self {
418       SKL::Ordinary       => matches!(s, SN::Link(_)
419                                        | SN::Client(_)
420                                        | SN::Server(_)
421                                        | SN::Common),
422
423       SKL::Limits         => matches!(s, SN::ServerLimit(_)
424                                        | SN::GlobalLimit),
425
426       SKL::ClientAgnostic => matches!(s, SN::Common
427                                        | SN::Server(_)),
428
429       SKL::Limited        => SKL::Ordinary.contains(s)
430                            | SKL::Limits  .contains(s),
431
432       SKL::ServerName     => matches!(s, SN::Common)
433                            | matches!(s, SN::Server(ServerName(name))
434                                          if name == SPECIAL_SERVER_SECTION),
435     }
436   }
437 }
438
439 impl Aggregate {
440   fn lookup_raw<'a,'s,S>(&'a self, key: &'static str, sections: S)
441                        -> Option<RawValRef<'a,'a,'s>>
442   where S: Iterator<Item=&'s SectionName>
443   {
444     for section in sections {
445       if let Some(raw) = self.sections
446         .get(section)
447         .and_then(|vars: &SectionMap| vars.get(key))
448       {
449         return Some(RawValRef {
450           raw: raw.raw.as_deref(),
451           loc: &raw.loc,
452           section, key,
453         })
454       }
455     }
456     None
457   }
458
459   #[throws(AE)]
460   pub fn establish_server_name(&self) -> ServerName {
461     let key = "server";
462     let raw = match self.lookup_raw(
463       key,
464       [ &SectionName::Common, &SN::special_server_section() ].iter().cloned()
465     ) {
466       Some(raw) => raw.try_map(|os| os.value())?,
467       None => SPECIAL_SERVER_SECTION,
468     };
469     ServerName(raw.into())
470   }
471 }
472
473 impl<'c> ResolveContext<'c> {
474   fn first_of_raw(&'c self, key: &'static str, sections: SectionKindList)
475                   -> Option<RawValRef<'c,'c,'c>> {
476     self.agg.lookup_raw(
477       key,
478       self.all_sections.iter()
479         .filter(|s| sections.contains(s))
480     )
481   }
482
483   #[throws(AE)]
484   fn first_of<T>(&self, key: &'static str, sections: SectionKindList)
485                  -> Option<T>
486   where T: Parseable
487   {
488     match self.first_of_raw(key, sections) {
489       None => None,
490       Some(raw) => Some(raw.try_map(Parseable::parse)?),
491     }
492   }
493
494   #[throws(AE)]
495   pub fn ordinary<T>(&self, key: &'static str) -> T
496   where T: Parseable
497   {
498     match self.first_of(key, SKL::Ordinary)? {
499       Some(y) => y,
500       None => Parseable::default_for_key(key)?,
501     }
502   }
503
504   #[throws(AE)]
505   pub fn limited<T>(&self, key: &'static str) -> T
506   where T: Parseable + Ord
507   {
508     let val = self.ordinary(key)?;
509     if let Some(limit) = self.first_of(key, SKL::Limits)? {
510       min(val, limit)
511     } else {
512       val
513     }
514   }
515
516   #[throws(AE)]
517   pub fn client<T>(&self, key: &'static str) -> T
518   where T: Parseable {
519     match self.end {
520       LinkEnd::Client => self.ordinary(key)?,
521       LinkEnd::Server => Parseable::default_for_key(key)?,
522     }
523   }
524   #[throws(AE)]
525   pub fn server<T>(&self, key: &'static str) -> T
526   where T: Parseable {
527     match self.end {
528       LinkEnd::Server => self.ordinary(key)?,
529       LinkEnd::Client => Parseable::default_for_key(key)?,
530     }
531   }
532
533   #[throws(AE)]
534   pub fn special_ipif(&self, key: &'static str) -> String {
535     match self.end {
536       LinkEnd::Client => self.ordinary(key)?,
537       LinkEnd::Server => {
538         self.first_of(key, SKL::ClientAgnostic)?
539           .unwrap_or_default()
540       },
541     }
542   }
543
544   #[throws(AE)]
545   pub fn special_server(&self, key: &'static str) -> ServerName {
546     self.server_name.clone()
547   }
548 }
549
550 #[throws(AE)]
551 pub fn read() {
552   let opts = config::Opts::from_args();
553
554   let agg = (||{
555     let mut agg = Aggregate::default();
556
557     agg.read_toplevel(&opts.config)?;
558     for extra in &opts.extra_config {
559       agg.read_extra(extra).context("extra config")?;
560     }
561
562     eprintln!("GOT {:#?}", agg);
563
564     Ok::<_,AE>(agg)
565   })().context("read configuration")?;
566
567   let server_name = agg.establish_server_name()?;
568
569   let link = LinkName {
570     server: "fooxxx".parse().unwrap(),
571     client: "127.0.0.1".parse().unwrap(),
572   };
573
574   let rctx = ResolveContext {
575     agg: &agg,
576     link: &link,
577     end: LinkEnd::Server,
578     server_name,
579     all_sections: vec![
580       SN::Link(link.clone()),
581       SN::Client(link.client.clone()),
582       SN::Server(link.server.clone()),
583       SN::Common,
584       SN::ServerLimit(link.server.clone()),
585       SN::GlobalLimit,
586     ],
587   };
588
589   let ic = InstanceConfig::resolve_instance(&rctx)
590     .context("resolve config xxx for")?;
591
592   eprintln!("{:?}", &ic);
593 }