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); +    } +}  | 
