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


             
             

                                                                        

                                      

     
                                



                                          
 






                                       
                                                            
                                          







                                    
                                                                                     


                               

                                               
         
                                     
                                                                                          

     
                                                                                              
                                       
 
                                          
                             
                        



                                                           
                                                 
                                                

                                    
                               
             
 


















                                                                           
 

                                                           
                                                                       

                 
 
                                             
                                                 
                                                      

                                                         
                        
                                                                                      
                 
             
         
 

















                                                                          
             

         






                                          



                                               
                               
                             
                                         


                               







                                              

         


              
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Copyright 2019 Hristo Venev

use crate::{fileutil, model};
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, fmt, io};

pub struct Device {
    ifname: OsString,
    tmpdir: PathBuf,
}

impl Device {
    #[inline]
    pub fn open(ifname: OsString, tmpdir: PathBuf) -> io::Result<Self> {
        let dev = Self { ifname, tmpdir };
        let _ = dev.get_public_key()?;
        Ok(dev)
    }

    fn wg_command() -> Command {
        let wg = match env::var_os("WG") {
            None => OsString::new(),
            Some(v) => v,
        };

        Command::new(if wg.is_empty() {
            OsStr::new("wg")
        } else {
            wg.as_os_str()
        })
    }

    pub fn get_public_key(&self) -> io::Result<model::Key> {
        let mut proc = Self::wg_command();
        proc.stdin(Stdio::null());
        proc.stdout(Stdio::piped());
        proc.arg("show");
        proc.arg(&self.ifname);
        proc.arg("public-key");

        let r = proc.output()?;
        if !r.status.success() {
            return Err(io::Error::new(io::ErrorKind::Other, "child process failed"));
        }

        let mut out = r.stdout;
        if out.last().copied() == Some(b'\n') {
            out.pop();
        }
        model::Key::from_base64(&out)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid public key"))
    }

    pub fn apply_diff(&mut self, old: &model::Config, new: &model::Config) -> io::Result<()> {
        let mut config = String::new();

        for (pubkey, conf) in &new.peers {
            let old_endpoint;
            let old_psk;
            if let Some(old_peer) = old.peers.get(pubkey) {
                if *old_peer == *conf {
                    continue;
                }
                old_endpoint = old_peer.endpoint;
                old_psk = old_peer.psk.as_ref();
            } else {
                old_endpoint = None;
                old_psk = None;
            }

            use fmt::Write;
            write!(
                config,
                "[Peer]\nPublicKey={}\nPersistentKeepalive={}\nAllowedIPs",
                pubkey, conf.keepalive
            )
            .unwrap();
            let mut delim = '=';
            for ip in &conf.ipv4 {
                config.push(delim);
                delim = ',';
                write!(config, "{}", ip).unwrap();
            }
            for ip in &conf.ipv6 {
                config.push(delim);
                delim = ',';
                write!(config, "{}", ip).unwrap();
            }
            config.push('\n');

            if old_endpoint != conf.endpoint {
                if let Some(ref endpoint) = conf.endpoint {
                    write!(config, "Endpoint={}\n", endpoint).unwrap();
                }
            }

            if old_psk != conf.psk.as_ref() {
                config.push_str("PresharedKey=");
                if let Some(psk) = conf.psk.as_ref() {
                    writeln!(config, "{}", psk).unwrap();
                    config.push('\n');
                } else {
                    config.push_str("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n");
                }
            }
        }

        {
            let mut config_file = fileutil::Writer::new_in(&self.tmpdir)?;
            io::Write::write_all(config_file.file(), config.as_bytes())?;
            let config_file = config_file.done();

            let mut proc = Self::wg_command();
            proc.stdin(Stdio::null());
            proc.stdout(Stdio::null());
            proc.arg("addconf");
            proc.arg(&self.ifname);
            proc.arg(config_file.path());

            let r = proc.status()?;
            if !r.success() {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    "`wg setconf' process failed",
                ));
            }
        }

        let mut proc = Self::wg_command();
        let mut any_removed = false;
        proc.stdin(Stdio::null());
        proc.stdout(Stdio::null());
        proc.arg("set");
        proc.arg(&self.ifname);

        for pubkey in old.peers.keys() {
            if new.peers.contains_key(pubkey) {
                continue;
            }
            any_removed = true;
            proc.arg("peer");
            proc.arg(pubkey.to_string());
            proc.arg("remove");
        }

        if any_removed {
            let r = proc.status()?;
            if !r.success() {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    "`wg set' process failed",
                ));
            }
        }

        Ok(())
    }
}