From 1b4d57871e90f9f04b237a93091e07072d5ce0d8 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Wed, 15 May 2019 16:55:07 +0200 Subject: [PATCH 01/12] Started wireguard config file parse --- src/main.rs | 1 + src/wireguard_config.rs | 85 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/wireguard_config.rs diff --git a/src/main.rs b/src/main.rs index 1b810f3..05cafb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use std::convert::TryFrom; use std::process::Command; use std::string::String; use wireguard::WireGuard; +mod wireguard_config; fn check_compliance(req: &Request) -> Result<(), Response> { if req.uri() != "/metrics" { diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs new file mode 100644 index 0000000..bb6c369 --- /dev/null +++ b/src/wireguard_config.rs @@ -0,0 +1,85 @@ +use std::collections::HashMap; + +#[derive(Debug, Default, Clone)] +pub(crate) struct PeerEntry { + pub public_key: String, + pub name: Option, +} + +pub(crate) fn parse<'a>(txt: &'a str) -> HashMap { + let mut ht = HashMap::new(); + + let mut name = ""; + txt.lines().fold("", |prev, cur| { + if cur == "[Peer]" { + if prev.chars().next() == Some('#') { + name = prev; + } + } else if cur.starts_with("PublicKey") { + // public key found, use it as key + // TODO we must strip the PublicKey = first ! + ht.insert( + cur.to_owned(), + PeerEntry { + public_key: cur.to_owned(), + name: Some(name.to_owned()), + }, + ); + } + + cur + }); + + ht +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEXT: &'static str = " +ListenPort = 51820 +PrivateKey = my_super_secret_private_key +# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE +# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE + +# OnePlus 6T +[Peer] +PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= +AllowedIPs = 10.70.0.2/32 + +# varch.local (laptop) +[Peer] +PublicKey = qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU= +AllowedIPs = 10.70.0.3/32 + +# cantarch +[Peer] +PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= +AllowedIPs = 10.70.0.4/32 + +# frcognoarch +[Peer] +PublicKey = MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA= +AllowedIPs = 10.70.0.50/32 + +# frcognowin10 +[Peer] +PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= +AllowedIPs = 10.70.0.40/32 + +# OnePlus 5T +[Peer] +PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= +AllowedIPs = 10.70.0.80/32 +"; + + #[test] + fn test_parse() { + let a = parse(TEXT); + println!("{:?}", a); + } + + #[test] + fn test_parse_and_serialize() {} +} From f01d55370d266109004955c3ee37214a41ce1847 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Thu, 16 May 2019 20:45:04 +0200 Subject: [PATCH 02/12] parsing done, tests to do --- src/wireguard_config.rs | 112 +++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index bb6c369..045a69c 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -1,34 +1,88 @@ use std::collections::HashMap; #[derive(Debug, Default, Clone)] -pub(crate) struct PeerEntry { - pub public_key: String, - pub name: Option, +pub(crate) struct PeerEntry<'a> { + pub public_key: &'a str, + pub allowed_ips: &'a str, + pub name: Option<&'a str>, } -pub(crate) fn parse<'a>(txt: &'a str) -> HashMap { +#[inline] +fn after_equals(s: &str) -> &str { + let mut p: usize = 0; + for c in s.chars().into_iter() { + if c == '=' { + return &s[p + 1..]; + } else { + p += c.len_utf8(); + } + } + s +} + +fn parse_peer_entry<'a>(lines: &[&'a str]) -> PeerEntry<'a> { + let mut public_key = ""; + let mut allowed_ips = ""; + let mut name = None; + + for line in lines { + if line.starts_with("PublicKey") { + public_key = after_equals(line).trim(); + } else if line.starts_with("AllowedIPs") { + allowed_ips = after_equals(line).trim(); + } else if line.starts_with("#") { + name = Some(line[1..].trim()); + } + } + + PeerEntry { + public_key, + allowed_ips, + name, + } +} + +pub(crate) fn parse<'a>(txt: &'a str) -> HashMap<&'a str, PeerEntry<'a>> { let mut ht = HashMap::new(); - let mut name = ""; - txt.lines().fold("", |prev, cur| { - if cur == "[Peer]" { - if prev.chars().next() == Some('#') { - name = prev; - } - } else if cur.starts_with("PublicKey") { - // public key found, use it as key - // TODO we must strip the PublicKey = first ! - ht.insert( - cur.to_owned(), - PeerEntry { - public_key: cur.to_owned(), - name: Some(name.to_owned()), - }, - ); - } + let mut v_blocks = Vec::new(); + let mut cur_block: Option> = None; - cur - }); + for line in txt.lines().into_iter() { + if line.starts_with("[") { + if let Some(inner_cur_block) = cur_block { + // close the block + v_blocks.push(inner_cur_block); + cur_block = None; + } + + if line == "[Peer]" { + // start a new block + cur_block = Some(Vec::new()); + } + } else { + // push the line if we are in a block (only if not empty) + if let Some(inner_cur_block) = &mut cur_block { + if line != "" { + inner_cur_block.push(line); + } + } + } + } + + if let Some(cur_block) = cur_block { + // we have a leftover block + v_blocks.push(cur_block); + } + + println!("v_blocks == {:?}", v_blocks); + + for block in &v_blocks { + let p = parse_peer_entry(block); + ht.insert(p.public_key, p); + } + + println!("ht == {:?}", ht); ht } @@ -43,33 +97,33 @@ PrivateKey = my_super_secret_private_key # PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE # PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE -# OnePlus 6T [Peer] +# OnePlus 6T PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= AllowedIPs = 10.70.0.2/32 -# varch.local (laptop) [Peer] +# varch.local (laptop) PublicKey = qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU= AllowedIPs = 10.70.0.3/32 -# cantarch [Peer] +# cantarch PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= AllowedIPs = 10.70.0.4/32 -# frcognoarch [Peer] +# frcognoarch PublicKey = MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA= AllowedIPs = 10.70.0.50/32 -# frcognowin10 [Peer] +# frcognowin10 PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= AllowedIPs = 10.70.0.40/32 -# OnePlus 5T [Peer] +# OnePlus 5T PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= AllowedIPs = 10.70.0.80/32 "; From 792a821de0edae6ad237fc885afe6b46c14b6bec Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 17 May 2019 10:17:06 +0200 Subject: [PATCH 03/12] Added sanity checks --- Cargo.lock | 232 ++++++++++++++++++++++------------------ Cargo.toml | 22 ++-- src/exporter_error.rs | 18 ++++ src/wireguard_config.rs | 37 ++++--- 4 files changed, 182 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index caf8713..28167bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,7 +29,7 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -41,15 +41,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -57,8 +56,8 @@ name = "backtrace-sys" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -77,17 +76,18 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -127,7 +127,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -147,10 +147,15 @@ name = "crossbeam-utils" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "env_logger" version = "0.6.1" @@ -168,7 +173,7 @@ name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -177,10 +182,10 @@ name = "failure_derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -209,7 +214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -217,19 +222,19 @@ name = "futures-cpupool" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "h2" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -245,7 +250,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-body" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -263,28 +279,30 @@ dependencies = [ [[package]] name = "hyper" -version = "0.12.27" +version = "0.12.29" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -298,13 +316,13 @@ name = "iovec" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -321,14 +339,9 @@ name = "lazy_static" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "lazycell" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "libc" -version = "0.2.51" +version = "0.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -345,7 +358,7 @@ name = "log" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -360,15 +373,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "mio" -version = "0.6.16" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -392,8 +404,8 @@ name = "net2" version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -407,7 +419,7 @@ name = "num_cpus" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -437,7 +449,7 @@ name = "parking_lot_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -446,7 +458,7 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -454,17 +466,17 @@ dependencies = [ [[package]] name = "prometheus_wireguard_exporter" -version = "0.1.0" +version = "1.2.0" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.12.27 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.29 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -478,7 +490,7 @@ name = "quote" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -487,12 +499,12 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -539,10 +551,10 @@ dependencies = [ [[package]] name = "rand_jitter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -554,7 +566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -633,7 +645,7 @@ dependencies = [ [[package]] name = "ryu" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -656,17 +668,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.90" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -674,9 +686,9 @@ name = "serde_json" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -706,22 +718,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.15.32" +version = "0.15.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "synstructure" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -738,7 +750,7 @@ name = "termion" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -765,35 +777,45 @@ name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tokio" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-trace-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-buf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tokio-current-thread" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -803,7 +825,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -812,7 +834,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -822,10 +844,10 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -840,7 +862,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -849,9 +871,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -864,7 +886,7 @@ dependencies = [ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -874,11 +896,11 @@ dependencies = [ [[package]] name = "tokio-timer" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -926,7 +948,7 @@ name = "want" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -992,19 +1014,20 @@ dependencies = [ "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f106c02a3604afcdc0df5d36cc47b44b55917dbaf3d808f71c163a0ddba64637" +"checksum backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "70af6de4789ac39587f100176ac7f704531e9e534b0f8676f658b3d909ce9a94" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -"checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83" -"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" @@ -1012,25 +1035,25 @@ dependencies = [ "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "62941eff9507c8177d448bd83a44d9b9760856e184081d8cd79ba9f03dd24981" +"checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "85ab6286db06040ddefb71641b50017c06874614001a134b423783e2db2920bd" +"checksum h2 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "2b53def7bb0253af7718036fe9338c15defd209136819464384f3a553e07481b" "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" +"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" -"checksum hyper 0.12.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4f2777434f26af6e4ce4fdcdccd3bed9d861d11e87bcbe72c0f51ddaca8ff848" +"checksum hyper 0.12.29 (registry+https://github.com/rust-lang/crates.io-index)" = "e2cd6adf83b3347d36e271f030621a8cf95fd1fd0760546b9fc5a24a0f1447c7" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" +"checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" -"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" +"checksum mio 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "049ba5ca2b63e837adeee724aa9e36b408ed593529dcc802aa96ca14bd329bdf" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" @@ -1039,7 +1062,7 @@ dependencies = [ "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" -"checksum proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)" = "ba92c84f814b3f9a44c5cfca7d2ad77fa10710867d2bbb1b3d175ab5f47daa12" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" @@ -1048,7 +1071,7 @@ dependencies = [ "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" @@ -1059,26 +1082,27 @@ dependencies = [ "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" "checksum rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ccc78bfd5acd7bf3e89cffcf899e5cb1a52d6fafa8dec2739ad70c9577a57288" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" -"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" +"checksum serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "a72e9b96fa45ce22a4bc23da3858dfccfd60acd28a25bcd328a98fdd6bea43fd" +"checksum serde_derive 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" = "101b495b109a3e3ca8c4cbe44cf62391527cdfb6ba15821c5ce80bcd5ea23f9f" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)" = "846620ec526c1599c070eff393bfeeeb88a93afa2513fc3b49f1fea84cf7b0ed" -"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cec6c34409089be085de9403ba2010b80e36938c9ca992c4f67f407bb13db0b1" +"checksum tokio 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "94a1f9396aec29d31bb16c24d155cfa144d1af91c40740125db3131bdaf76da8" +"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" "checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" @@ -1086,7 +1110,7 @@ dependencies = [ "checksum tokio-sync 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5b2f843ffdf8d6e1f90bddd48da43f99ab071660cd92b7ec560ef3cdfd7a409a" "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" "checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" -"checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" +"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" "checksum tokio-trace-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "350c9edade9830dc185ae48ba45667a445ab59f6167ef6d0254ec9d2430d9dd3" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" diff --git a/Cargo.toml b/Cargo.toml index eb0b3ce..ceef78f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus_wireguard_exporter" -version = "0.1.0" +version = "1.2.0" authors = ["Francesco Cogno "] description = "Prometheus WireGuard Exporter" edition = "2018" @@ -16,13 +16,13 @@ categories = ["database"] [dependencies] -log = "0.4.6" -env_logger = "0.6.1" -futures = "0.1.26" -clap = "2.33.0" -serde_json = "1.0.39" -serde = "1.0.90" -serde_derive = "1.0.90" -failure = "0.1.5" -hyper = "0.12.27" -http = "0.1.17" +log = "*" +env_logger = "*" +futures = "*" +clap = "*" +serde_json = "*" +serde = "*" +serde_derive = "*" +failure = "*" +hyper = "*" +http = "*" diff --git a/src/exporter_error.rs b/src/exporter_error.rs index dbb726c..b3bc0c2 100644 --- a/src/exporter_error.rs +++ b/src/exporter_error.rs @@ -1,3 +1,12 @@ +#[derive(Debug, Fail)] +pub(crate) enum PeerEntryParseError { + #[fail(display = "PublicKey entry not found in lines: {:?}", lines)] + PublicKeyNotFound { lines: Vec }, + + #[fail(display = "AllowedIPs entry not found in lines: {:?}", lines)] + AllowedIPsEntryNotFound { lines: Vec }, +} + #[derive(Debug, Fail)] pub(crate) enum ExporterError { #[allow(dead_code)] @@ -24,6 +33,15 @@ pub(crate) enum ExporterError { #[fail(display = "int conversion error: {}", e)] ParseInt { e: std::num::ParseIntError }, + + #[fail(display = "PeerEntry parse error: {}", e)] + PeerEntryParseError { e: PeerEntryParseError }, +} + +impl From for ExporterError { + fn from(e: PeerEntryParseError) -> Self { + ExporterError::PeerEntryParseError { e } + } } impl From for ExporterError { diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index 045a69c..46a4078 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -1,4 +1,6 @@ +use crate::exporter_error::PeerEntryParseError; use std::collections::HashMap; +use std::convert::TryFrom; #[derive(Debug, Default, Clone)] pub(crate) struct PeerEntry<'a> { @@ -8,10 +10,10 @@ pub(crate) struct PeerEntry<'a> { } #[inline] -fn after_equals(s: &str) -> &str { +fn after_char(s: &str, c_split: char) -> &str { let mut p: usize = 0; for c in s.chars().into_iter() { - if c == '=' { + if c == c_split { return &s[p + 1..]; } else { p += c.len_utf8(); @@ -20,29 +22,40 @@ fn after_equals(s: &str) -> &str { s } -fn parse_peer_entry<'a>(lines: &[&'a str]) -> PeerEntry<'a> { +fn parse_peer_entry<'a>(lines: &[&'a str]) -> Result, PeerEntryParseError> { let mut public_key = ""; let mut allowed_ips = ""; let mut name = None; for line in lines { if line.starts_with("PublicKey") { - public_key = after_equals(line).trim(); + public_key = after_char(line, '=').trim(); } else if line.starts_with("AllowedIPs") { - allowed_ips = after_equals(line).trim(); + allowed_ips = after_char(line, '=').trim(); } else if line.starts_with("#") { name = Some(line[1..].trim()); } } - PeerEntry { - public_key, - allowed_ips, - name, + // Sanity checks + if public_key == "" { + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) + } else if allowed_ips == "" { + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) + } else { + Ok(PeerEntry { + public_key, + allowed_ips, + name, // name can be None + }) } } -pub(crate) fn parse<'a>(txt: &'a str) -> HashMap<&'a str, PeerEntry<'a>> { +pub(crate) fn parse<'a>( + txt: &'a str, +) -> Result>, PeerEntryParseError> { let mut ht = HashMap::new(); let mut v_blocks = Vec::new(); @@ -78,13 +91,13 @@ pub(crate) fn parse<'a>(txt: &'a str) -> HashMap<&'a str, PeerEntry<'a>> { println!("v_blocks == {:?}", v_blocks); for block in &v_blocks { - let p = parse_peer_entry(block); + let p = parse_peer_entry(block)?; ht.insert(p.public_key, p); } println!("ht == {:?}", ht); - ht + Ok(ht) } #[cfg(test)] From 70178cb8d681099ad834793b34d05f9de6c7f688 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 17 May 2019 11:00:53 +0200 Subject: [PATCH 04/12] Added proper tests (but more should be addedgit add --all :/) --- src/wireguard_config.rs | 202 +++++++++++++++++++++++++++------------- 1 file changed, 136 insertions(+), 66 deletions(-) diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index 46a4078..ecc1a85 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -1,4 +1,5 @@ use crate::exporter_error::PeerEntryParseError; +use log::debug; use std::collections::HashMap; use std::convert::TryFrom; @@ -22,82 +23,96 @@ fn after_char(s: &str, c_split: char) -> &str { s } -fn parse_peer_entry<'a>(lines: &[&'a str]) -> Result, PeerEntryParseError> { - let mut public_key = ""; - let mut allowed_ips = ""; - let mut name = None; +impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { + type Error = PeerEntryParseError; - for line in lines { - if line.starts_with("PublicKey") { - public_key = after_char(line, '=').trim(); - } else if line.starts_with("AllowedIPs") { - allowed_ips = after_char(line, '=').trim(); - } else if line.starts_with("#") { - name = Some(line[1..].trim()); + fn try_from(lines: &[&'a str]) -> Result, Self::Error> { + let mut public_key = ""; + let mut allowed_ips = ""; + let mut name = None; + + for line in lines { + if line.starts_with("PublicKey") { + public_key = after_char(line, '=').trim(); + } else if line.starts_with("AllowedIPs") { + allowed_ips = after_char(line, '=').trim(); + } else if line.starts_with("#") { + // since the pound sign is 1 byte the below slice will work + name = Some(line[1..].trim()); + } } - } - // Sanity checks - if public_key == "" { - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); - Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) - } else if allowed_ips == "" { - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); - Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) - } else { - Ok(PeerEntry { - public_key, - allowed_ips, - name, // name can be None - }) + // Sanity checks + // If there are more than one PublicKey or AllowedIPs we won't catch it. But + // WireGuard won't be working either so we can live with this simplification. + if public_key == "" { + // we return a owned String for ergonomics. This will allocate but it's ok since it's not supposed + // to happen :) + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) + } else if allowed_ips == "" { + let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) + } else { + Ok(PeerEntry { + public_key, + allowed_ips, + name, // name can be None + }) + } } } -pub(crate) fn parse<'a>( - txt: &'a str, -) -> Result>, PeerEntryParseError> { - let mut ht = HashMap::new(); +#[derive(Debug, Default, Clone)] +pub(crate) struct PeerEntryHashMap<'a>(HashMap<&'a str, PeerEntry<'a>>); - let mut v_blocks = Vec::new(); - let mut cur_block: Option> = None; +impl<'a> TryFrom<&'a str> for PeerEntryHashMap<'a> { + type Error = PeerEntryParseError; - for line in txt.lines().into_iter() { - if line.starts_with("[") { - if let Some(inner_cur_block) = cur_block { - // close the block - v_blocks.push(inner_cur_block); - cur_block = None; - } + fn try_from(txt: &'a str) -> Result { + let mut hm = HashMap::new(); - if line == "[Peer]" { - // start a new block - cur_block = Some(Vec::new()); - } - } else { - // push the line if we are in a block (only if not empty) - if let Some(inner_cur_block) = &mut cur_block { - if line != "" { - inner_cur_block.push(line); + let mut v_blocks = Vec::new(); + let mut cur_block: Option> = None; + + for line in txt.lines().into_iter() { + if line.starts_with("[") { + if let Some(inner_cur_block) = cur_block { + // close the block + v_blocks.push(inner_cur_block); + cur_block = None; + } + + if line == "[Peer]" { + // start a new block + cur_block = Some(Vec::new()); + } + } else { + // push the line if we are in a block (only if not empty) + if let Some(inner_cur_block) = &mut cur_block { + if line != "" { + inner_cur_block.push(line); + } } } } + + if let Some(cur_block) = cur_block { + // we have a leftover block + v_blocks.push(cur_block); + } + + debug!("v_blocks == {:?}", v_blocks); + + for block in &v_blocks { + let p: PeerEntry = PeerEntry::try_from(&block as &[&str])?; + hm.insert(p.public_key, p); + } + + debug!("hm == {:?}", hm); + + Ok(PeerEntryHashMap(hm)) } - - if let Some(cur_block) = cur_block { - // we have a leftover block - v_blocks.push(cur_block); - } - - println!("v_blocks == {:?}", v_blocks); - - for block in &v_blocks { - let p = parse_peer_entry(block)?; - ht.insert(p.public_key, p); - } - - println!("ht == {:?}", ht); - - Ok(ht) } #[cfg(test)] @@ -139,14 +154,69 @@ AllowedIPs = 10.70.0.40/32 # OnePlus 5T PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= AllowedIPs = 10.70.0.80/32 +"; + + const TEXT_NOPK: &'static str = " +ListenPort = 51820 +PrivateKey = my_super_secret_private_key +# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE +# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE + +[Peer] +# OnePlus 6T +PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= +AllowedIPs = 10.70.0.2/32 + +[Peer] +# varch.local (laptop) +AllowedIPs = 10.70.0.3/32 + +[Peer] +# cantarch +PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= +AllowedIPs = 10.70.0.4/32 +"; + + const TEXT_AIP: &'static str = " +ListenPort = 51820 +PrivateKey = my_super_secret_private_key +# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE +# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE + +[Peer] +# OnePlus 6T +PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= +AllowedIPs = 10.70.0.2/32 + +[Peer] +# varch.local (laptop) +AllowedIPs = 10.70.0.3/32 +PublicKey = 6S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk= + +[Peer] +# cantarch +PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008= "; #[test] - fn test_parse() { - let a = parse(TEXT); + fn test_parse_ok() { + let a: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT).unwrap(); println!("{:?}", a); } #[test] - fn test_parse_and_serialize() {} + #[should_panic( + expected = "PublicKeyNotFound { lines: [\"# varch.local (laptop)\", \"AllowedIPs = 10.70.0.3/32\"] }" + )] + fn test_parse_no_public_key() { + let _: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT_NOPK).unwrap(); + } + + #[test] + #[should_panic( + expected = "AllowedIPsEntryNotFound { lines: [\"# cantarch\", \"PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=\"] }" + )] + fn test_parse_no_allowed_ips() { + let _: PeerEntryHashMap = PeerEntryHashMap::try_from(TEXT_AIP).unwrap(); + } } From 5b983d5e346aba136de50da0bfb052dc0e2ba5b2 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Fri, 17 May 2019 19:32:35 +0200 Subject: [PATCH 05/12] adding switch --- src/main.rs | 41 ++++++++++++++++++++++++++++++++--------- src/options.rs | 13 +++++++++++-- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 05cafb6..0583842 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,10 +63,13 @@ fn handle_request( fn perform_request( _req: Request, - _options: &Options, + options: &Options, ) -> impl Future, Error = ExporterError> { trace!("perform_request"); + // this is needed to satisfy the borrow checker + let options = options.clone(); + done( Command::new("wg") .arg("show") @@ -76,14 +79,29 @@ fn perform_request( ) .from_err() .and_then(|output| { - done(String::from_utf8(output.stdout)) - .from_err() - .and_then(|output_str| { - trace!("{}", output_str); - done(WireGuard::try_from(&output_str as &str)) + if let Some(extract_names_config_file) = options.extract_names_config_file { + Either::A( + done(String::from_utf8(output.stdout)) .from_err() - .and_then(|wg| ok(Response::new(Body::from(wg.render())))) - }) + .and_then(|output_str| { + trace!("{}", output_str); + done(WireGuard::try_from(&output_str as &str)) + .from_err() + .and_then(|wg| ok(Response::new(Body::from(wg.render())))) + }), + ) + } else { + Either::B( + done(String::from_utf8(output.stdout)) + .from_err() + .and_then(|output_str| { + trace!("{}", output_str); + done(WireGuard::try_from(&output_str as &str)) + .from_err() + .and_then(|wg| ok(Response::new(Body::from(wg.render())))) + }), + ) + } }) } @@ -94,7 +112,7 @@ fn main() { .arg( Arg::with_name("port") .short("p") - .help("exporter port (default 9576)") + .help("exporter port") .default_value("9576") .takes_value(true), ) @@ -104,6 +122,11 @@ fn main() { .help("verbose logging") .takes_value(false), ) + .arg( + Arg::with_name("extract_names_config_file") + .short("n") + .help("If set, the exporter will look in the specified WireGuard config file for peer names (must be in [Peer] definition and be a comment)") + .takes_value(true)) .get_matches(); let options = Options::from_claps(&matches); diff --git a/src/options.rs b/src/options.rs index b24488e..4c39713 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,12 +1,21 @@ #[derive(Debug, Clone)] pub(crate) struct Options { pub verbose: bool, + pub extract_names_config_file: Option, } impl Options { pub fn from_claps(matches: &clap::ArgMatches<'_>) -> Options { - Options { - verbose: matches.is_present("verbose"), + if let Some(e) = matches.value_of("extract_names_config_file") { + Options { + verbose: matches.is_present("verbose"), + extract_names_config_file: Some(e.to_owned()), + } + } else { + Options { + verbose: matches.is_present("verbose"), + extract_names_config_file: None, + } } } } From be9bf63faca5eb0cb93c7b81da8ccb20082fe600 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 09:45:20 +0200 Subject: [PATCH 06/12] Simpified code --- src/main.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0583842..57b275a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use std::process::Command; use std::string::String; use wireguard::WireGuard; mod wireguard_config; +use wireguard_config::PeerEntryHashMap; fn check_compliance(req: &Request) -> Result<(), Response> { if req.uri() != "/metrics" { @@ -61,6 +62,18 @@ fn handle_request( }) } +fn wg_with_text( + wg_config_str: &str, + wg_output: ::std::process::Output, +) -> Result, ExporterError> { + let pehm = PeerEntryHashMap::try_from(wg_config_str)?; + println!("pehm == {:?}", pehm); + + let wg_output_string = String::from_utf8(wg_output.stdout)?; + let wg = WireGuard::try_from(&wg_output_string as &str)?; + Ok(Response::new(Body::from(wg.render()))) +} + fn perform_request( _req: Request, options: &Options, @@ -81,14 +94,9 @@ fn perform_request( .and_then(|output| { if let Some(extract_names_config_file) = options.extract_names_config_file { Either::A( - done(String::from_utf8(output.stdout)) + done(::std::fs::read_to_string(extract_names_config_file)) .from_err() - .and_then(|output_str| { - trace!("{}", output_str); - done(WireGuard::try_from(&output_str as &str)) - .from_err() - .and_then(|wg| ok(Response::new(Body::from(wg.render())))) - }), + .and_then(|wg_config_string| wg_with_text(&wg_config_string as &str, output)), ) } else { Either::B( From 82188d14945f5f6da082233d0863afc6859caad9 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 10:09:02 +0200 Subject: [PATCH 07/12] before live test --- src/main.rs | 8 ++-- src/wireguard.rs | 54 ++++++++++++++++++++++----- src/wireguard_config.rs | 81 ++++++++++++++++++++--------------------- 3 files changed, 87 insertions(+), 56 deletions(-) diff --git a/src/main.rs b/src/main.rs index 57b275a..e36a223 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use std::process::Command; use std::string::String; use wireguard::WireGuard; mod wireguard_config; -use wireguard_config::PeerEntryHashMap; +use wireguard_config::peer_entry_hashmap_try_from; fn check_compliance(req: &Request) -> Result<(), Response> { if req.uri() != "/metrics" { @@ -66,8 +66,8 @@ fn wg_with_text( wg_config_str: &str, wg_output: ::std::process::Output, ) -> Result, ExporterError> { - let pehm = PeerEntryHashMap::try_from(wg_config_str)?; - println!("pehm == {:?}", pehm); + let pehm = peer_entry_hashmap_try_from(wg_config_str)?; + trace!("pehm == {:?}", pehm); let wg_output_string = String::from_utf8(wg_output.stdout)?; let wg = WireGuard::try_from(&wg_output_string as &str)?; @@ -115,7 +115,7 @@ fn perform_request( fn main() { let matches = clap::App::new("prometheus_wireguard_exporter") - .version("0.1") + .version("1.2.0") .author("Francesco Cogno ") .arg( Arg::with_name("port") diff --git a/src/wireguard.rs b/src/wireguard.rs index 25505f1..2817b4f 100644 --- a/src/wireguard.rs +++ b/src/wireguard.rs @@ -1,5 +1,6 @@ use crate::exporter_error::ExporterError; use crate::render_to_prometheus::RenderToPrometheus; +use crate::wireguard_config::PeerEntryHashMap; use log::{debug, trace}; use std::collections::HashMap; use std::convert::TryFrom; @@ -124,20 +125,47 @@ impl TryFrom<&str> for WireGuard { } } -impl RenderToPrometheus for WireGuard { - fn render(&self) -> String { +impl WireGuard { + fn render_with_names(&self, pehm: Option<&PeerEntryHashMap>) -> String { let mut latest_handshakes = Vec::new(); let mut sent_bytes = Vec::new(); let mut received_bytes = Vec::new(); - for (interface, endpoints) in self.interfaces.iter() { - for endpoint in endpoints { - // only show remote endpoints - if let Endpoint::Remote(ep) = endpoint { - debug!("{:?}", ep); - sent_bytes.push(format!("wireguard_sent_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.sent_bytes)); - received_bytes.push(format!("wireguard_received_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.received_bytes)); - latest_handshakes.push(format!("wireguard_latest_handshake_seconds{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ::std::time::SystemTime::now().duration_since(ep.latest_handshake).unwrap().as_secs())); + if let Some(pehm) = pehm { + // here we try to add the "friendly" name looking in the hashmap + for (interface, endpoints) in self.interfaces.iter() { + for endpoint in endpoints { + // only show remote endpoints + if let Endpoint::Remote(ep) = endpoint { + debug!("{:?}", ep); + if let Some(ep_friendly_name) = pehm.get(&ep.public_key as &str) { + if let Some(ep_friendly_name) = ep_friendly_name.name { + sent_bytes.push(format!("wireguard_sent_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\", friendly_name=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep_friendly_name, ep.sent_bytes)); + received_bytes.push(format!("wireguard_received_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\", friendly_name=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep_friendly_name, ep.received_bytes)); + latest_handshakes.push(format!("wireguard_latest_handshake_seconds{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\", friendly_name=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep_friendly_name, ::std::time::SystemTime::now().duration_since(ep.latest_handshake).unwrap().as_secs())); + } else { + sent_bytes.push(format!("wireguard_sent_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.sent_bytes)); + received_bytes.push(format!("wireguard_received_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.received_bytes)); + latest_handshakes.push(format!("wireguard_latest_handshake_seconds{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ::std::time::SystemTime::now().duration_since(ep.latest_handshake).unwrap().as_secs())); + } + } else { + sent_bytes.push(format!("wireguard_sent_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.sent_bytes)); + received_bytes.push(format!("wireguard_received_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.received_bytes)); + latest_handshakes.push(format!("wireguard_latest_handshake_seconds{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ::std::time::SystemTime::now().duration_since(ep.latest_handshake).unwrap().as_secs())); + } + } + } + } + } else { + for (interface, endpoints) in self.interfaces.iter() { + for endpoint in endpoints { + // only show remote endpoints + if let Endpoint::Remote(ep) = endpoint { + debug!("{:?}", ep); + sent_bytes.push(format!("wireguard_sent_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.sent_bytes)); + received_bytes.push(format!("wireguard_received_bytes{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ep.received_bytes)); + latest_handshakes.push(format!("wireguard_latest_handshake_seconds{{inteface=\"{}\", public_key=\"{}\", local_ip=\"{}\", local_subnet=\"{}\"}} {}\n", interface, ep.public_key, ep.local_ip, ep.local_subnet, ::std::time::SystemTime::now().duration_since(ep.latest_handshake).unwrap().as_secs())); + } } } } @@ -173,6 +201,12 @@ impl RenderToPrometheus for WireGuard { } } +impl RenderToPrometheus for WireGuard { + fn render(&self) -> String { + self.render_with_names(None) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index ecc1a85..2af6701 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -63,56 +63,53 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { } } -#[derive(Debug, Default, Clone)] -pub(crate) struct PeerEntryHashMap<'a>(HashMap<&'a str, PeerEntry<'a>>); +pub(crate) type PeerEntryHashMap<'a> = (HashMap<&'a str, PeerEntry<'a>>); -impl<'a> TryFrom<&'a str> for PeerEntryHashMap<'a> { - type Error = PeerEntryParseError; +pub(crate) fn peer_entry_hashmap_try_from<'a>( + txt: &'a str, +) -> Result { + let mut hm = HashMap::new(); - fn try_from(txt: &'a str) -> Result { - let mut hm = HashMap::new(); + let mut v_blocks = Vec::new(); + let mut cur_block: Option> = None; - let mut v_blocks = Vec::new(); - let mut cur_block: Option> = None; + for line in txt.lines().into_iter() { + if line.starts_with("[") { + if let Some(inner_cur_block) = cur_block { + // close the block + v_blocks.push(inner_cur_block); + cur_block = None; + } - for line in txt.lines().into_iter() { - if line.starts_with("[") { - if let Some(inner_cur_block) = cur_block { - // close the block - v_blocks.push(inner_cur_block); - cur_block = None; - } - - if line == "[Peer]" { - // start a new block - cur_block = Some(Vec::new()); - } - } else { - // push the line if we are in a block (only if not empty) - if let Some(inner_cur_block) = &mut cur_block { - if line != "" { - inner_cur_block.push(line); - } + if line == "[Peer]" { + // start a new block + cur_block = Some(Vec::new()); + } + } else { + // push the line if we are in a block (only if not empty) + if let Some(inner_cur_block) = &mut cur_block { + if line != "" { + inner_cur_block.push(line); } } } - - if let Some(cur_block) = cur_block { - // we have a leftover block - v_blocks.push(cur_block); - } - - debug!("v_blocks == {:?}", v_blocks); - - for block in &v_blocks { - let p: PeerEntry = PeerEntry::try_from(&block as &[&str])?; - hm.insert(p.public_key, p); - } - - debug!("hm == {:?}", hm); - - Ok(PeerEntryHashMap(hm)) } + + if let Some(cur_block) = cur_block { + // we have a leftover block + v_blocks.push(cur_block); + } + + debug!("v_blocks == {:?}", v_blocks); + + for block in &v_blocks { + let p: PeerEntry = PeerEntry::try_from(&block as &[&str])?; + hm.insert(p.public_key, p); + } + + debug!("hm == {:?}", hm); + + Ok(hm) } #[cfg(test)] From a12ecb50fbdce79cce8e870290fe7a1253f74722 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 10:21:32 +0200 Subject: [PATCH 08/12] Corrected wrong method --- src/main.rs | 2 +- src/wireguard.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index e36a223..522a5c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn wg_with_text( let wg_output_string = String::from_utf8(wg_output.stdout)?; let wg = WireGuard::try_from(&wg_output_string as &str)?; - Ok(Response::new(Body::from(wg.render()))) + Ok(Response::new(Body::from(wg.render_with_names(Some(&pehm))))) } fn perform_request( diff --git a/src/wireguard.rs b/src/wireguard.rs index 2817b4f..2f390d9 100644 --- a/src/wireguard.rs +++ b/src/wireguard.rs @@ -126,7 +126,7 @@ impl TryFrom<&str> for WireGuard { } impl WireGuard { - fn render_with_names(&self, pehm: Option<&PeerEntryHashMap>) -> String { + pub(crate) fn render_with_names(&self, pehm: Option<&PeerEntryHashMap>) -> String { let mut latest_handshakes = Vec::new(); let mut sent_bytes = Vec::new(); let mut received_bytes = Vec::new(); From f629730f4ff43ea492de84e077d98e02dc2ba87a Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 10:56:40 +0200 Subject: [PATCH 09/12] Updated docs --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++--- extra/01.png | Bin 0 -> 86616 bytes 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 extra/01.png diff --git a/README.md b/README.md index 7de7310..43091c5 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ [![Crate](https://img.shields.io/crates/v/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter) [![cratedown](https://img.shields.io/crates/d/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter) [![cratelastdown](https://img.shields.io/crates/dv/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter) -[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/0.1.0) -[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/0.1.0) -[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/0.1.0.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/0.1.0.svg) +[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/1.2.0) +[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/1.2.0) +[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/1.2.0.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/1.2.0.svg) ## Intro A Prometheus exporter for [WireGuard](https://www.wireguard.com), written in Rust. This tool exports the `wg show all dump` results in a format that [Prometheus](https://prometheus.io/) can understand. The exporter is very light on your server resources, both in terms of memory and CPU usage. -![](extra/00.png) +![](extra/01.png) ## Prerequisites @@ -43,9 +43,102 @@ Start the binary with `-h` to get the complete syntax. The parameters are: | -- | -- | -- | -- | -- | | `-v` | no | | | Enable verbose mode. | `-p` | no | any valid port number | 9576 | Specify the service port. This is the port your Prometheus instance should point to. +| `-n` | no | path to the wireguard configuration file | This flag adds the *friendly_name* attribute to the exported entries. See [Friendly names](#friendly-names) for more details. Once started, the tool will listen on the specified port (or the default one, 9576, if not specified) and return a Prometheus valid response at the url `/metrics`. So to check if the tool is working properly simply browse the `http://localhost:9576/metrics` (or whichever port you choose). +## Friendly Names + +Starting from version 1.2 you can instruct the exporter to append a *friendly name* to the exported entries. This can make the output more understandable than using the public keys. For example this is the standard output: + +``` +# HELP wireguard_sent_bytes Bytes sent to the peer +# TYPE wireguard_sent_bytes counter +wireguard_sent_bytes{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32"} 13500788 +wireguard_sent_bytes{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32"} 7975608 +# HELP wireguard_received_bytes Bytes received from the peer +# TYPE wireguard_received_bytes counter +wireguard_received_bytes{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32"} 50007604 +wireguard_received_bytes{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32"} 0 +wireguard_received_bytes{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32"} 0 +wireguard_received_bytes{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32"} 0 +wireguard_received_bytes{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32"} 0 +wireguard_received_bytes{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32"} 0 +wireguard_received_bytes{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32"} 182505560 +# HELP wireguard_latest_handshake_seconds Seconds from the last handshake +# TYPE wireguard_latest_handshake_seconds gauge +wireguard_latest_handshake_seconds{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32"} 50780 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32"} 63459 +``` + +And this is the one augmented with friendly names: + +``` +# HELP wireguard_sent_bytes Bytes sent to the peer +# TYPE wireguard_sent_bytes counter +wireguard_sent_bytes{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32", friendly_name="OnePlus 6T"} 13500788 +wireguard_sent_bytes{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32", friendly_name="varch.local (laptop)"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32", friendly_name="cantarch"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32", friendly_name="frcognoarch"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32", friendly_name="frcognowin10"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32", friendly_name="OnePlus 5T"} 0 +wireguard_sent_bytes{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32", friendly_name="folioarch"} 7975608 +# HELP wireguard_received_bytes Bytes received from the peer +# TYPE wireguard_received_bytes counter +wireguard_received_bytes{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32", friendly_name="OnePlus 6T"} 50007604 +wireguard_received_bytes{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32", friendly_name="varch.local (laptop)"} 0 +wireguard_received_bytes{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32", friendly_name="cantarch"} 0 +wireguard_received_bytes{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32", friendly_name="frcognoarch"} 0 +wireguard_received_bytes{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32", friendly_name="frcognowin10"} 0 +wireguard_received_bytes{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32", friendly_name="OnePlus 5T"} 0 +wireguard_received_bytes{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32", friendly_name="folioarch"} 182505560 +# HELP wireguard_latest_handshake_seconds Seconds from the last handshake +# TYPE wireguard_latest_handshake_seconds gauge +wireguard_latest_handshake_seconds{inteface="wg0", public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=", local_ip="10.70.0.2", local_subnet="32", friendly_name="OnePlus 6T"} 50780 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=", local_ip="10.70.0.3", local_subnet="32", friendly_name="varch.local (laptop)"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=", local_ip="10.70.0.4", local_subnet="32", friendly_name="cantarch"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=", local_ip="10.70.0.50", local_subnet="32", friendly_name="frcognoarch"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=", local_ip="10.70.0.40", local_subnet="32", friendly_name="frcognowin10"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=", local_ip="10.70.0.80", local_subnet="32", friendly_name="OnePlus 5T"} 1558341167 +wireguard_latest_handshake_seconds{inteface="wg0", public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=", local_ip="10.70.0.5", local_subnet="32", friendly_name="folioarch"} 63459 +``` + +In order for this to work, you need to add comments to your wireguard configuration file (below the `[Peer]` definition). The comment will be interpreted as `friendly_name` and added to the entry exported to Prometheus. Note that this is not a standard but, since it's a comment, will not interfere with WireGuard in any way. For example this is how you edit your WireGuard configuration file: + +``` +[Peer] +PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= +AllowedIPs = 10.70.0.40/32 + +[Peer] +PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= +AllowedIPs = 10.70.0.80/32 +``` + +``` +[Peer] +# frcognowin10 +PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= +AllowedIPs = 10.70.0.40/32 + +[Peer] +# OnePlus 5T +PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= +AllowedIPs = 10.70.0.80/32 +``` + +As you can see, all you need to do is to add the friendly name as comment (and enable the flag since this feature is opt-in). + ### Systemd service file Now add the exporter to the Prometheus exporters as usual. I recommend to start it as a service. It's necessary to run it as root (if there is a non-root way to call `wg show all dump` please let me know). My systemd service file is like this one: @@ -60,7 +153,7 @@ After=network-online.target User=root Group=root Type=simple -ExecStart=/usr/local/bin/prometheus_wireguard_exporter +ExecStart=/usr/local/bin/prometheus_wireguard_exporter -n /etc/wireguard/wg0.conf [Install] WantedBy=multi-user.target diff --git a/extra/01.png b/extra/01.png new file mode 100644 index 0000000000000000000000000000000000000000..62baf1e96d3fbc2367bc615a8285b1f02a03b7a3 GIT binary patch literal 86616 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%0A%?L#9-tc|^^!W!n_)n{?biyE=b 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 G?)QIVW|Q6k literal 0 HcmV?d00001 From b4cfb04e9db6c57f5d7885b8937c94f7715d8296 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 10:58:35 +0200 Subject: [PATCH 10/12] Corrected table column --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43091c5..92bc59b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Start the binary with `-h` to get the complete syntax. The parameters are: | -- | -- | -- | -- | -- | | `-v` | no | | | Enable verbose mode. | `-p` | no | any valid port number | 9576 | Specify the service port. This is the port your Prometheus instance should point to. -| `-n` | no | path to the wireguard configuration file | This flag adds the *friendly_name* attribute to the exported entries. See [Friendly names](#friendly-names) for more details. +| `-n` | no | path to the wireguard configuration file | | This flag adds the *friendly_name* attribute to the exported entries. See [Friendly names](#friendly-names) for more details. Once started, the tool will listen on the specified port (or the default one, 9576, if not specified) and return a Prometheus valid response at the url `/metrics`. So to check if the tool is working properly simply browse the `http://localhost:9576/metrics` (or whichever port you choose). From ffd01fdba2b445bfe6a13a85b4f9d30a7b0f34a3 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 11:01:04 +0200 Subject: [PATCH 11/12] clippy'd --- src/wireguard_config.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wireguard_config.rs b/src/wireguard_config.rs index 2af6701..a64f352 100644 --- a/src/wireguard_config.rs +++ b/src/wireguard_config.rs @@ -36,7 +36,7 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { public_key = after_char(line, '=').trim(); } else if line.starts_with("AllowedIPs") { allowed_ips = after_char(line, '=').trim(); - } else if line.starts_with("#") { + } else if line.starts_with('#') { // since the pound sign is 1 byte the below slice will work name = Some(line[1..].trim()); } @@ -48,10 +48,10 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { if public_key == "" { // we return a owned String for ergonomics. This will allocate but it's ok since it's not supposed // to happen :) - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + let lines_owned: Vec = lines.iter().map(|line| line.to_string()).collect(); Err(PeerEntryParseError::PublicKeyNotFound { lines: lines_owned }) } else if allowed_ips == "" { - let lines_owned: Vec = lines.into_iter().map(|line| line.to_string()).collect(); + let lines_owned: Vec = lines.iter().map(|line| line.to_string()).collect(); Err(PeerEntryParseError::AllowedIPsEntryNotFound { lines: lines_owned }) } else { Ok(PeerEntry { @@ -65,8 +65,8 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> { pub(crate) type PeerEntryHashMap<'a> = (HashMap<&'a str, PeerEntry<'a>>); -pub(crate) fn peer_entry_hashmap_try_from<'a>( - txt: &'a str, +pub(crate) fn peer_entry_hashmap_try_from( + txt: &str, ) -> Result { let mut hm = HashMap::new(); @@ -74,7 +74,7 @@ pub(crate) fn peer_entry_hashmap_try_from<'a>( let mut cur_block: Option> = None; for line in txt.lines().into_iter() { - if line.starts_with("[") { + if line.starts_with('[') { if let Some(inner_cur_block) = cur_block { // close the block v_blocks.push(inner_cur_block); From 13eddbec912c57ca4e472b2e917adefe428df960 Mon Sep 17 00:00:00 2001 From: Francesco Cogno Date: Mon, 20 May 2019 11:06:26 +0200 Subject: [PATCH 12/12] Fixed dependencies --- Cargo.lock | 6 +++--- Cargo.toml | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28167bb..b326960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -173,7 +173,7 @@ name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1014,7 +1014,7 @@ dependencies = [ "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum backtrace 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "70af6de4789ac39587f100176ac7f704531e9e534b0f8676f658b3d909ce9a94" +"checksum backtrace 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "f92d5d536fa03dc3d93711d97bac1fae2eb59aba467ca4c6600c0119da614f51" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" diff --git a/Cargo.toml b/Cargo.toml index ceef78f..e1f0132 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,13 @@ categories = ["database"] [dependencies] -log = "*" -env_logger = "*" -futures = "*" -clap = "*" -serde_json = "*" -serde = "*" -serde_derive = "*" -failure = "*" -hyper = "*" -http = "*" +log = "0.4.6" +env_logger = "0.6.1" +futures = "0.1.27" +clap = "2.33.0" +serde_json = "1.0.39" +serde = "1.0.91" +serde_derive = "1.0.91" +failure = "0.1.5" +hyper = "0.12.29" +http = "0.1.17"