aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHristo Venev <hristo@venev.name>2019-03-19 10:14:23 +0200
committerHristo Venev <hristo@venev.name>2019-03-19 10:14:23 +0200
commite143b186a3eba9c03af20156ad3a2e729c638dc9 (patch)
tree20f966ffc8981d90ab5a94c3c6a86172f92fd3f2
parent517040083e7796f59c47f8afe6a782417b132c20 (diff)
BREAKING CHANGES
ifname is now passed as an argument before the config file. own_public_key is obtained from the interface.
-rw-r--r--src/config.rs19
-rw-r--r--src/main.rs144
-rw-r--r--src/wg.rs63
-rw-r--r--wgconfd@.service15
4 files changed, 166 insertions, 75 deletions
diff --git a/src/config.rs b/src/config.rs
index eab10fe..00874c2 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -17,7 +17,6 @@ pub struct Source {
#[serde(deny_unknown_fields)]
#[derive(serde_derive::Serialize, serde_derive::Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct PeerConfig {
- pub own_public_key: String,
#[serde(default = "default_min_keepalive")]
pub min_keepalive: u32,
#[serde(default = "default_max_keepalive")]
@@ -35,17 +34,11 @@ pub struct UpdateConfig {
#[serde(deny_unknown_fields)]
#[derive(serde_derive::Serialize, serde_derive::Deserialize, Clone, Debug)]
pub struct Config {
- pub ifname: String,
- #[serde(default = "default_wg_command")]
- pub wg_command: String,
- #[serde(default = "default_curl_command")]
- pub curl_command: String,
-
#[serde(flatten)]
- pub peers: PeerConfig,
+ pub peer_config: PeerConfig,
#[serde(flatten)]
- pub update: UpdateConfig,
+ pub update_config: UpdateConfig,
pub sources: Vec<Source>,
}
@@ -62,14 +55,6 @@ impl PeerConfig {
}
}
-fn default_wg_command() -> String {
- "wg".to_owned()
-}
-
-fn default_curl_command() -> String {
- "curl".to_owned()
-}
-
fn default_min_keepalive() -> u32 {
10
}
diff --git a/src/main.rs b/src/main.rs
index 0ef17aa..f5ebb26 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,7 +18,7 @@ struct Source {
config: config::Source,
data: Option<proto::Source>,
next_update: Instant,
- backoff: Option<u32>,
+ backoff: Option<Duration>,
}
impl Source {
@@ -38,25 +38,31 @@ pub struct Device {
update_config: config::UpdateConfig,
sources: Vec<Source>,
current: wg::Config,
- curl_command: String,
}
impl Device {
- pub fn new(c: config::Config) -> Device {
- let dev = wg::Device::new(c.ifname, c.wg_command);
- let current = wg::ConfigBuilder::new(&c.peers).build();
- Device {
+ pub fn new(ifname: String, c: config::Config) -> io::Result<Device> {
+ let dev = wg::Device::new(ifname)?;
+
+ Ok(Device {
dev,
- peer_config: c.peers,
- update_config: c.update,
+ peer_config: c.peer_config,
+ update_config: c.update_config,
sources: c.sources.into_iter().map(Source::new).collect(),
- current,
- curl_command: c.curl_command,
- }
+ current: wg::Config::default(),
+ })
}
- fn make_config(&self, ts: SystemTime) -> (wg::Config, Vec<wg::ConfigError>, SystemTime) {
- let mut next_update = ts + Duration::from_secs(3600);
+ fn refresh_period(&self) -> Duration {
+ Duration::from_secs(u64::from(self.update_config.refresh_period))
+ }
+
+ fn make_config(
+ &self,
+ public_key: &str,
+ ts: SystemTime,
+ ) -> (wg::Config, Vec<wg::ConfigError>, SystemTime) {
+ let mut t_cfg = ts + self.refresh_period();
let mut sources: Vec<(&Source, &proto::SourceConfig)> = vec![];
for src in self.sources.iter() {
if let Some(ref data) = src.data {
@@ -67,7 +73,7 @@ impl Device {
if ts >= next.update_at {
Some(&next.config)
} else {
- next_update = next_update.min(next.update_at);
+ t_cfg = t_cfg.min(next.update_at);
None
}
})
@@ -76,7 +82,7 @@ impl Device {
}
}
- let mut cfg = wg::ConfigBuilder::new(&self.peer_config);
+ let mut cfg = wg::ConfigBuilder::new(public_key, &self.peer_config);
let mut errs = vec![];
for (src, sc) in sources.iter() {
for peer in sc.servers.iter() {
@@ -90,26 +96,28 @@ impl Device {
}
let cfg = cfg.build();
- (cfg, errs, next_update)
+ (cfg, errs, t_cfg)
}
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;
+ let refresh = self.refresh_period();
+ let mut now = Instant::now();
+ let mut t_refresh = now + refresh;
+
for src in self.sources.iter_mut() {
if now < src.next_update {
- next_update = next_update.min(src.next_update);
+ t_refresh = t_refresh.min(src.next_update);
continue;
}
- let r = match fetch_source(&self.curl_command, &src.config.url) {
+ let r = fetch_source(&src.config.url);
+ now = Instant::now();
+ let r = match r {
Ok(r) => {
eprintln!("<6>Updated [{}]", &src.config.url);
src.data = Some(r);
src.backoff = None;
- src.next_update = after_refresh;
+ src.next_update = now + refresh;
continue;
}
Err(r) => r,
@@ -120,20 +128,22 @@ impl Device {
let b = src.backoff.unwrap_or(if src.data.is_some() {
refresh / 3
} else {
- u32::min(10, refresh / 10)
+ Duration::from_secs(10).min(refresh / 10)
});
+ src.next_update = now + b;
+ t_refresh = t_refresh.min(src.next_update);
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 now = Instant::now();
let sysnow = SystemTime::now();
- let (config, errors, upd_time) = self.make_config(sysnow);
- let time_to_upd = upd_time
+ let public_key = self.dev.get_public_key()?;
+ let (config, errors, t_cfg) = self.make_config(&public_key, sysnow);
+ let time_to_cfg = t_cfg
.duration_since(sysnow)
.unwrap_or(Duration::from_secs(0));
- next_update = next_update.min(now + time_to_upd);
+ let t_cfg = now + time_to_cfg;
if config != self.current {
eprintln!("<5>Applying configuration update");
@@ -143,27 +153,49 @@ impl Device {
self.dev.apply_diff(&self.current, &config)?;
self.current = config;
}
- eprintln!("<6>Next configuration update after {:?}", time_to_upd);
- Ok(next_update)
+ Ok(if t_cfg < t_refresh {
+ eprintln!("<6>Next configuration update after {:?}", time_to_cfg);
+ t_cfg
+ } else if t_refresh > now {
+ eprintln!("<6>Next refresh after {:?}", t_refresh.duration_since(now));
+ t_refresh
+ } else {
+ now
+ })
}
}
-fn fetch_source(curl_command: &str, url: &str) -> io::Result<proto::Source> {
+fn fetch_source(url: &str) -> io::Result<proto::Source> {
+ use std::env;
+ use std::ffi::{OsStr, OsString};
use std::process::{Command, Stdio};
- let out = Command::new(curl_command)
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::null())
- .arg("--fail")
- .arg("--fail-early")
- .arg("--")
- .arg(url)
- .output()?;
+ let curl = match env::var_os("CURL") {
+ None => OsString::new(),
+ Some(v) => v,
+ };
+ let mut proc = Command::new(if curl.is_empty() {
+ OsStr::new("curl")
+ } else {
+ curl.as_os_str()
+ });
+
+ proc.stdin(Stdio::null());
+ proc.stdout(Stdio::piped());
+ proc.stderr(Stdio::null());
+ proc.arg("--fail");
+ proc.arg("--fail-early");
+ proc.arg("--");
+ proc.arg(url);
+
+ let out = proc.output()?;
if !out.status.success() {
- return Err(io::Error::new(io::ErrorKind::Other, format!("Failed to download [{}]", url)));
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ format!("Failed to download [{}]", url),
+ ));
}
let mut de = serde_json::Deserializer::from_slice(&out.stdout);
@@ -185,14 +217,20 @@ fn main() {
use std::{env, process, thread};
let args: Vec<String> = env::args().collect();
- if args.len() != 2 {
+ if args.len() != 3 {
let arg0 = if !args.is_empty() { &args[0] } else { "wgconf" };
eprintln!("<1>Usage:");
- eprintln!("<1> {} CONFIG", arg0);
+ eprintln!("<1> {} IFNAME CONFIG", arg0);
process::exit(1);
}
- let config = match load_config(&args[1]) {
+ let mut args = args.into_iter();
+ let _ = args.next().unwrap();
+ let ifname = args.next().unwrap();
+ let config_path = args.next().unwrap();
+ assert!(args.next().is_none());
+
+ let config = match load_config(&config_path) {
Ok(c) => c,
Err(e) => {
eprintln!("<1>Failed to load config: {}", e);
@@ -200,7 +238,14 @@ fn main() {
}
};
- let mut dev = Device::new(config);
+ let mut dev = match Device::new(ifname, config) {
+ Ok(dev) => dev,
+ Err(e) => {
+ eprintln!("<1>Failed to open device: {}", e);
+ process::exit(1);
+ }
+ };
+
loop {
let tm = match dev.update() {
Ok(t) => t,
@@ -210,8 +255,9 @@ fn main() {
}
};
let now = Instant::now();
- let sleep = tm.duration_since(now);
- println!("Sleeping for {:?}", sleep);
- thread::sleep(sleep);
+ if tm > now {
+ let sleep = tm.duration_since(now);
+ thread::sleep(sleep);
+ }
}
}
diff --git a/src/wg.rs b/src/wg.rs
index 94f9603..1de5574 100644
--- a/src/wg.rs
+++ b/src/wg.rs
@@ -8,6 +8,10 @@ use hash_map::HashMap;
use std::collections::hash_map;
use std::{error, fmt, io};
+use std::env;
+use std::ffi::{OsStr, OsString};
+use std::process::{Command, Stdio};
+
#[derive(Debug)]
pub struct ConfigError {
pub url: String,
@@ -59,15 +63,25 @@ pub struct Config {
peers: HashMap<String, Peer>,
}
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ peers: HashMap::new(),
+ }
+ }
+}
+
pub struct ConfigBuilder<'a> {
peers: HashMap<String, Peer>,
+ public_key: &'a str,
pc: &'a config::PeerConfig,
}
impl<'a> ConfigBuilder<'a> {
- pub fn new(pc: &'a config::PeerConfig) -> Self {
+ pub fn new(public_key: &'a str, pc: &'a config::PeerConfig) -> Self {
ConfigBuilder {
peers: HashMap::new(),
+ public_key,
pc,
}
}
@@ -144,7 +158,7 @@ impl<'a> ConfigBuilder<'a> {
return;
}
- if p.peer.public_key == self.pc.own_public_key {
+ if p.peer.public_key == self.public_key {
return;
}
@@ -169,7 +183,7 @@ impl<'a> ConfigBuilder<'a> {
return;
}
- let ent = if p.base == self.pc.own_public_key {
+ let ent = if p.base == self.public_key {
self.insert_with(err, s, &p.peer, |_| {})
} else {
match self.peers.get_mut(&p.base) {
@@ -186,18 +200,49 @@ impl<'a> ConfigBuilder<'a> {
pub struct Device {
ifname: String,
- wg_command: String,
}
impl Device {
- pub fn new(ifname: String, wg_command: String) -> Self {
- Device { ifname, wg_command }
+ pub fn new(ifname: String) -> io::Result<Self> {
+ Ok(Device { ifname })
}
- pub fn apply_diff(&mut self, old: &Config, new: &Config) -> io::Result<()> {
- use std::process::{Command, Stdio};
+ 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<String> {
+ let mut proc = Device::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 proc = Command::new(&self.wg_command);
+ let mut out = r.stdout;
+ if out.ends_with(b"\n") {
+ out.remove(out.len() - 1);
+ }
+ String::from_utf8(out)
+ .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid public key"))
+ }
+
+ pub fn apply_diff(&mut self, old: &Config, new: &Config) -> io::Result<()> {
+ let mut proc = Device::wg_command();
proc.stdin(Stdio::piped());
proc.arg("set");
proc.arg(&self.ifname);
diff --git a/wgconfd@.service b/wgconfd@.service
new file mode 100644
index 0000000..d6e4127
--- /dev/null
+++ b/wgconfd@.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Configure WireGuard interface
+Wants=network-pre.target
+Before=network-pre.target nftables.service systemd-networkd.service NetworkManager.service
+
+[Service]
+Type=simple
+CapabilityBoundingSet=CAP_NET_ADMIN
+ExecStartPre=/usr/bin/wg setconf %i /etc/wireguard/%i.conf
+ExecStart=/usr/local/bin/wgconfd %i /etc/wireguard/%i.json
+StandardError=journal
+SyslogLevelPrefix=true
+
+[Install]
+WantedBy=multi-user.target