diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ad57895 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,203 @@ +#[macro_use] +extern crate arrayref; + +use ::std::io; +use ::std::time::{Duration, SystemTime, Instant}; + +mod bin; +mod ip; +mod proto; +mod config; +mod wg; + +struct Source { + config: config::Source, + data: Option<proto::Source>, + next_update: Instant, + backoff: Option<u32>, +} + +impl Source { + fn new(config: config::Source) -> Source { + Source { + config, + data: None, + next_update: Instant::now(), + backoff: None, + } + } +} + +pub struct Device { + dev: wg::Device, + peer_config: config::PeerConfig, + update_config: config::UpdateConfig, + sources: Vec<Source>, + current: wg::Config, +} + +impl Device { + pub fn new(c: config::Config) -> Device { + Device { + dev: wg::Device::new(c.ifname, c.wg_command), + peer_config: c.peers, + update_config: c.update, + sources: c.sources.into_iter().map(Source::new).collect(), + current: wg::Config::new(), + } + } + + fn make_config(&self, ts: SystemTime) -> (wg::Config, Vec<wg::ConfigError>, SystemTime) { + let mut cfg = wg::Config::new(); + let mut next_update = ts + Duration::from_secs(3600); + let mut errs = vec![]; + for src in self.sources.iter() { + if let Some(data) = &src.data { + let sc = data.next.as_ref().and_then(|next| { + if ts >= next.update_at { + Some(&next.config) + } else { + next_update = next_update.min(next.update_at); + None + } + }).unwrap_or(&data.config); + for peer in sc.peers.iter() { + cfg.add_peer(&mut errs, &self.peer_config, &src.config, peer); + } + } + } + (cfg, errs, next_update) + } + + pub fn update(&mut self) -> io::Result<Instant> { + let now = Instant::now(); + let refresh = self.update_config.refresh_period; + let after_refresh = now + Duration::from_secs(u64::from(refresh)); + let mut next_update = after_refresh; + for src in self.sources.iter_mut() { + if now < src.next_update { + next_update = next_update.min(src.next_update); + continue; + } + + let r = fetch_source(&src.config.url); + let r = match r { + Ok(r) => { + eprintln!("<6>Updated [{}]", &src.config.url); + src.data = Some(r); + src.backoff = None; + src.next_update = after_refresh; + continue; + } + Err(r) => r, + }; + + eprintln!("<3>Failed to update [{}]: {}", &src.config.url, &r); + + let b = src.backoff.unwrap_or(if src.data.is_some() { + refresh / 3 + } else { + u32::min(10, refresh / 10) + }); + let b = (b + b / 3).min(refresh); + src.backoff = Some(b); + src.next_update = now + Duration::from_secs(u64::from(b)); + next_update = next_update.min(src.next_update); + } + + let sysnow = SystemTime::now(); + let (config, errors, upd_time) = self.make_config(sysnow); + let time_to_upd = upd_time.duration_since(sysnow).unwrap_or(Duration::from_secs(0)); + next_update = next_update.min(now + time_to_upd); + + if config != self.current { + eprintln!("<5>Applying configuration update"); + for err in errors.iter() { + eprintln!("<{}>{}", if err.important { '4' } else { '5' }, err); + } + self.dev.apply_diff(&self.current, &config)?; + self.current = config; + } + eprintln!("<6>Next configuration update after {:?}", time_to_upd); + + Ok(next_update) + } +} + +fn fetch_source(url: &str) -> io::Result<proto::Source> { + use ::curl::easy::Easy; + + let mut res = Vec::<u8>::new(); + + { + let mut req = Easy::new(); + req.url(url)?; + + { + let mut tr = req.transfer(); + tr.write_function(|data| { + res.extend_from_slice(data); + Ok(data.len()) + })?; + tr.perform()?; + } + + let code = req.response_code()?; + if code != 0 && code != 200 { + return Err(io::Error::new(io::ErrorKind::Other, format!("HTTP error {}", code))); + } + } + + let mut de = serde_json::Deserializer::from_slice(&res); + let r = serde::Deserialize::deserialize(&mut de)?; + Ok(r) +} + +fn load_config(path: &str) -> io::Result<config::Config> { + use std::fs; + use ::serde_json; + + let config_file = fs::File::open(path)?; + let rd = io::BufReader::new(config_file); + let mut de = serde_json::Deserializer::from_reader(rd); + Ok(serde::Deserialize::deserialize(&mut de)?) +} + +fn main() { + use ::std::{env, thread, process}; + + let args: Vec<String> = env::args().into_iter().collect(); + if args.len() != 2 { + let arg0 = if args.len() >= 1 { + &args[0] + } else { + "wgconf" + }; + eprintln!("<1>Usage:"); + eprintln!("<1> {} CONFIG", arg0); + process::exit(1); + } + + let config = match load_config(&args[1]) { + Ok(c) => c, + Err(e) => { + eprintln!("<1>Failed to load config: {}", e); + process::exit(1); + } + }; + + let mut dev = Device::new(config); + loop { + let tm = match dev.update() { + Ok(t) => t, + Err(e) => { + eprintln!("<1>{}", e); + process::exit(1); + } + }; + let now = Instant::now(); + let sleep = tm.duration_since(now); + println!("Sleeping for {:?}", sleep); + thread::sleep(sleep); + } +} |