aboutsummaryrefslogblamecommitdiff
path: root/src/main.rs
blob: cfd9c8226da5bb98e7c324e5a5012a23139e6ad0 (plain) (tree)
1
2
3
4
5
6
7
8
9



                              

                          


                      
                                
                       




                                
         
 
           

            
          

       

                                                              






                                                    

                                                                   

 





















































                                                                                    
                                                               




















                                                                             
                              



                                                                     


     








                                                                          
      

     
 




                                                                                 
     
 
 











                                                                                 
     
 
                                          


                                                         






















                                                                                    

         

                              
 
                                                                    
                                                                          



                                                                      

                                                         
                     


         
          
                                   


                                      
                         


                                 



                                               

     
 






                                                                                    
     












                                       
           
                                       

                                          

                              
                                                                                                  






                                                                   

                       


                                           


                                      




                          

                                          
 
// Copyright 2019 Hristo Venev
//
// See COPYING.

#![deny(rust_2018_idioms)]

#[macro_use]
extern crate arrayref;

use std::ffi::{OsStr, OsString};
use std::time::Instant;
use std::{env, process, thread};

#[cfg(feature = "toml")]
use std::{fs, io};
#[cfg(feature = "toml")]
use toml;

mod config;
mod manager;
mod model;
mod proto;
mod wg;

#[cfg(feature = "toml")]
fn file_config(path: OsString) -> io::Result<config::Config> {
    let mut data = String::new();
    {
        use io::Read;
        let mut config_file = fs::File::open(path)?;
        config_file.read_to_string(&mut data)?;
    }
    let mut de = toml::Deserializer::new(&data);
    serde::Deserialize::deserialize(&mut de)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

fn cli_config(args: &mut impl Iterator<Item = OsString>) -> Option<config::Config> {
    use std::str::FromStr;

    let mut cfg = config::Config::default();

    let mut cur_src: Option<&mut config::Source> = None;
    while let Some(key) = args.next() {
        let arg;

        if let Some(ref mut s) = cur_src {
            if key == "psk" {
                arg = args.next()?;
                let arg = arg.to_str()?;
                s.psk = Some(model::Key::from_str(arg).ok()?);
                continue;
            }
            if key == "ipv4" {
                arg = args.next()?;
                let arg = arg.to_str()?;
                for arg in arg.split(',') {
                    s.ipv4.insert(model::Ipv4Net::from_str(arg).ok()?);
                }
                continue;
            }
            if key == "ipv6" {
                arg = args.next()?;
                let arg = arg.to_str()?;
                for arg in arg.split(',') {
                    s.ipv6.insert(model::Ipv6Net::from_str(arg).ok()?);
                }
                continue;
            }
            if key == "required" {
                s.required = true;
                continue;
            }
        }
        cur_src = None;

        if key == "min_keepalive" {
            arg = args.next()?;
            let arg = arg.to_str()?;
            cfg.peer_config.min_keepalive = u32::from_str(arg).ok()?;
            continue;
        }
        if key == "max_keepalive" {
            arg = args.next()?;
            let arg = arg.to_str()?;
            cfg.peer_config.max_keepalive = u32::from_str(arg).ok()?;
            continue;
        }
        if key == "refresh_sec" {
            arg = args.next()?;
            let arg = arg.to_str()?;
            cfg.updater.refresh_sec = u32::from_str(arg).ok()?;
            continue;
        }
        if key == "source" {
            let name = args.next()?.into_string().ok()?;
            let url = args.next()?.into_string().ok()?;
            cur_src = Some(cfg.sources.entry(name).or_insert(config::Source {
                url,
                psk: None,
                ipv4: model::Ipv4Set::new(),
                ipv6: model::Ipv6Set::new(),
                required: false,
            }));
            continue;
        }

        return None;
    }

    Some(cfg)
}

fn usage(argv0: &str) -> i32 {
    eprintln!(
        "<1>Invalid arguments. See `{} --help` for more information",
        argv0
    );
    1
}

fn help(argv0: &str, _args: &mut impl Iterator<Item = OsString>) -> i32 {
    print!(
        "\
Usage:
    {} IFNAME CONFIG         - run daemon on iterface
    {} --check-source PATH   - validate source JSON
    {} --cmdline IFNAME ...  - run daemon using config passed as arguments
",
        argv0, argv0, argv0
    );
    1
}

fn maybe_get_var(out: &mut Option<impl From<OsString>>, var: impl AsRef<OsStr>) {
    let var = var.as_ref();
    if let Some(s) = env::var_os(var) {
        env::remove_var(var);
        *out = Some(s.into());
    }
}

#[cfg(feature = "toml")]
fn run_with_file(argv0: &str, args: &mut impl Iterator<Item = OsString>) -> i32 {
    let ifname = match args.next() {
        Some(v) => v,
        None => return usage(argv0),
    };
    let path = match args.next() {
        Some(v) => v,
        None => return usage(argv0),
    };
    if args.next().is_some() {
        return usage(argv0);
    }

    let config = match file_config(path) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("<1>Failed to load config: {}", e);
            return 1;
        }
    };
    run_daemon(ifname, config)
}

#[cfg(not(feature = "toml"))]
fn run_with_file(_: &str, _: &mut impl Iterator<Item = OsString>) -> i32 {
    eprintln!("<1>Config loading not supported");
    1
}

fn run_with_cmdline(argv0: &str, args: &mut impl Iterator<Item = OsString>) -> i32 {
    let ifname = match args.next() {
        Some(v) => v,
        None => return usage(argv0),
    };

    let config = match cli_config(args) {
        Some(c) => c,
        None => {
            eprintln!("<1>Invalid config");
            return 1;
        }
    };
    run_daemon(ifname, config)
}

fn run_daemon(ifname: OsString, mut config: config::Config) -> i32 {
    maybe_get_var(&mut config.updater.cache_directory, "CACHE_DIRECTORY");
    maybe_get_var(&mut config.runtime_directory, "RUNTIME_DIRECTORY");

    let mut m = match manager::Manager::new(ifname, config) {
        Ok(m) => m,
        Err(e) => {
            eprintln!("<1>Failed to open device: {}", e);
            return 1;
        }
    };

    loop {
        let tm = match m.update() {
            Ok(t) => t,
            Err(e) => {
                eprintln!("<1>{}", e);
                return 1;
            }
        };
        let now = Instant::now();
        if tm > now {
            let sleep = tm.duration_since(now);
            thread::sleep(sleep);
        }
    }
}

fn run_check_source(argv0: &str, args: &mut impl Iterator<Item = OsString>) -> i32 {
    let path = match args.next() {
        Some(v) => v,
        None => return usage(argv0),
    };
    if args.next().is_some() {
        return usage(argv0);
    }

    match manager::load_source(&path) {
        Ok(_) => {
            println!("OK");
            0
        }
        Err(e) => {
            println!("{}", e);
            1
        }
    }
}

fn main() {
    let mut iter_args = env::args_os();
    let argv0 = iter_args.next().unwrap();
    let argv0 = argv0.to_string_lossy();

    let mut args = Vec::new();
    let mut run: for<'a> fn(&'a str, &'a mut std::vec::IntoIter<OsString>) -> i32 = run_with_file;
    let mut parse_args = true;
    for arg in iter_args {
        if !parse_args || !arg.to_string_lossy().starts_with('-') {
            args.push(arg);
        } else if arg == "--" {
            parse_args = false;
        } else if arg == "-h" || arg == "--help" {
            run = help;
            break;
        } else if arg == "--check-source" {
            run = run_check_source;
            parse_args = false;
        } else if arg == "--cmdline" {
            run = run_with_cmdline;
            parse_args = false;
        } else {
            usage(&argv0);
        }
    }

    let mut args = args.into_iter();
    process::exit(run(&argv0, &mut args));
}