diff --git a/Cargo.toml b/Cargo.toml index bd35a49..4a142e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus_wireguard_exporter" -version = "3.1.1" +version = "3.2.0" authors = ["Francesco Cogno "] description = "Prometheus WireGuard Exporter" edition = "2018" diff --git a/README.md b/README.md index 887acb6..695ccf7 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) -[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.1.1) -[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.1.1) +[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.2.0) +[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.2.0) [![Build Status](https://travis-ci.org/MindFlavor/prometheus_wireguard_exporter.svg?branch=master)](https://travis-ci.org/MindFlavor/prometheus_wireguard_exporter) -[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.1.1.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.1.1.svg) +[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.2.0.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.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. +A Prometheus exporter for [WireGuard](https://www.wireguard.com), written in Rust. This tool exports the `wg show all dump` (or `wg show dump` if you specify a config file) 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. Starting from release [2.0.2](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/2.0.2) this exporter supports IPv6 addressess too (thanks to [Maximilian Bosch](https://github.com/Ma27)'s PR [#5](https://github.com/MindFlavor/prometheus_wireguard_exporter/pull/5)). From release [3.0.0](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.0.0) the exporter allows two label modes: one is to dump every allowed ip in a single label (called `allowed_ips`) along with their subnets. The second one is to create a pair of labels for each allowed ip/subnet pair (called `allowed_ip_0`/`allowed_subnet_0`, `allowed_ip_1`/`allowed_subnet_1` and so on for every allowed ip). The default if the single label mode but you can enable the second mode by specifying the `-s` switch at startup. Thank you [Toon Schoenmakers](https://github.com/schoentoon) for this solution (see [https://github.com/MindFlavor/prometheus_wireguard_exporter/issues/8](https://github.com/MindFlavor/prometheus_wireguard_exporter/issues/8)). diff --git a/src/main.rs b/src/main.rs index d060a60..30708b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use clap; use clap::{crate_authors, crate_name, crate_version, Arg}; use futures::future::{done, ok, Either, Future}; use hyper::{Body, Request, Response}; -use log::{info, trace}; +use log::{debug, info, trace}; use std::env; mod options; use options::Options; @@ -25,14 +25,13 @@ use std::sync::Arc; fn wg_with_text( wg_config_str: &str, - wg_output: ::std::process::Output, + wg_output_str: &str, options: Arc, ) -> Result, ExporterError> { 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)?; + let wg = WireGuard::try_from(wg_output_str)?; Ok(Response::new(Body::from(wg.render_with_names( Some(&pehm), options.separate_allowed_ips, @@ -45,45 +44,75 @@ fn perform_request( options: &Arc, ) -> impl Future, Error = failure::Error> { trace!("perform_request"); - // this is needed to satisfy the borrow checker let options = options.clone(); + debug!("options == {:?}", options); + + //let interface = options.get_interface(); + + let interface_str = match options.get_interface() { + Some(interface_str) => interface_str, + None => "all", + } + .to_owned(); + + debug!("using inteface_str {}", interface_str); done( Command::new("wg") .arg("show") - .arg("all") + .arg(&interface_str) .arg("dump") .output(), ) .from_err() .and_then(move |output| { - if let Some(extract_names_config_file) = &options.extract_names_config_file { - Either::A( - done(::std::fs::read_to_string(extract_names_config_file)) - .from_err() - .and_then(|wg_config_string| { - wg_with_text(&wg_config_string as &str, output, options) - }), - ) - } else { - Either::B(done(String::from_utf8(output.stdout)).from_err().and_then( - move |output_str| { - trace!("{}", output_str); - done(WireGuard::try_from(&output_str as &str)) - .from_err() - .and_then(move |wg| { - ok(Response::new(Body::from(wg.render_with_names( - None, - options.separate_allowed_ips, - options.export_remote_ip_and_port, - )))) - }) - }, - )) - } + done(String::from_utf8(output.stdout)) + .from_err() + .and_then(move |output_str| { + trace!("wg show output == {}", output_str); + + // the output of wg show is different if we use all or we specify an interface. + // In the first case the first column will be the interface name. In the second case + // the interface name will be omitted. We need to compensate for the skew somehow (one + // column less in the second case). We solve this prepending the interface name in every + // line so the output of the second case will be equal to the first case. + let output_str = if interface_str != "all" { + debug!("injecting {} to the wg show output", interface_str); + let mut result = String::new(); + for s in output_str.lines() { + result.push_str(&format!("{}\t{}\n", interface_str, s)); + } + result + } else { + output_str + }; + + if let Some(extract_names_config_file) = &options.extract_names_config_file { + Either::A( + done(::std::fs::read_to_string(extract_names_config_file)) + .from_err() + .and_then(move |wg_config_string| { + wg_with_text(&wg_config_string as &str, &output_str, options) + }), + ) + } else { + Either::B({ + trace!("{}", output_str); + done(WireGuard::try_from(&output_str as &str)) + .from_err() + .and_then(move |wg| { + ok(Response::new(Body::from(wg.render_with_names( + None, + options.separate_allowed_ips, + options.export_remote_ip_and_port, + )))) + }) + }) + } + }) + .from_err() }) - .from_err() } fn main() { diff --git a/src/options.rs b/src/options.rs index 91fcd00..48f01f6 100644 --- a/src/options.rs +++ b/src/options.rs @@ -24,4 +24,46 @@ impl Options { } } } + + pub fn get_interface(&self) -> Option<&str> { + if let Some(config_file) = &self.extract_names_config_file { + let path = std::path::Path::new(config_file); + if let Some(file_stem) = path.file_stem() { + file_stem.to_str() + } else { + None + } + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_interface_some() { + let options = Options { + verbose: true, + separate_allowed_ips: false, + extract_names_config_file: Some("/etc/wireguard/wg0.conf".to_owned()), + export_remote_ip_and_port: true, + }; + + assert_eq!(options.get_interface(), Some("wg0")); + } + + #[test] + fn test_interface_none() { + let options = Options { + verbose: true, + separate_allowed_ips: false, + extract_names_config_file: None, + export_remote_ip_and_port: true, + }; + + assert_eq!(options.get_interface(), None); + } }