aboutsummaryrefslogtreecommitdiff
path: root/src/wg.rs
blob: 879251be573009188b4befcbd9d975f7cbf2082a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Copyright 2019 Hristo Venev

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

pub struct Device {
    ifname: OsString,
}

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

    pub 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.ends_with(b"\n") {
            out.remove(out.len() - 1);
        }
        model::Key::from_bytes(&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 proc = Self::wg_command();
        proc.arg("set");
        proc.arg(&self.ifname);

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

            proc.arg("peer");
            proc.arg(pubkey.to_string());

            proc.arg("persistent-keepalive");
            proc.arg(conf.keepalive.to_string());

            if old_endpoint != conf.endpoint {
                if let Some(ref endpoint) = conf.endpoint {
                    proc.arg("endpoint");
                    proc.arg(endpoint.to_string());
                }
            }

            if let Some(psk) = &conf.psk {
                proc.arg("preshared-key");
                proc.arg(psk.path());
            }

            let mut ips = String::new();
            {
                use std::fmt::Write;
                for ip in &conf.ipv4 {
                    if !ips.is_empty() {
                        ips.push(',');
                    }
                    write!(ips, "{}", ip).unwrap();
                }
                for ip in &conf.ipv6 {
                    if !ips.is_empty() {
                        ips.push(',');
                    }
                    write!(ips, "{}", ip).unwrap();
                }
            }

            proc.arg("allowed-ips");
            proc.arg(ips);
        }

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

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