Initial commit
This commit is contained in:
commit
143a450778
12 changed files with 1657 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
*.vim
|
1105
Cargo.lock
generated
Normal file
1105
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "prometheus_wireguard_exporter"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Francesco Cogno <francesco.cogno@outlook.com>"]
|
||||||
|
description = "Prometheus WireGuard Exporter"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/MindFlavor/prometheus_wireguard_exporter"
|
||||||
|
documentation = "https://github.com/MindFlavor/prometheus_wireguard_exporter"
|
||||||
|
homepage = "https://github.com/MindFlavor/prometheus_wireguard_exporter"
|
||||||
|
|
||||||
|
keywords = ["prometheus", "exporter", "wireguard"]
|
||||||
|
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"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Francesco Cogno
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
67
README.md
Normal file
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Prometheus WireGuard Exporter
|
||||||
|
|
||||||
|
[![legal](https://img.shields.io/github/license/mindflavor/prometheus_wireguard_exporter.svg)](LICENSE)
|
||||||
|
|
||||||
|
[![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)
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
* You need [Rust](https://www.rust-lang.org/) to compile this code. Simply follow the instructions on Rust's website to install the toolchain. If you get weird errors while compiling please try and update your Rust version first (I have developed it on `rustc 1.35.0-nightly (8159f389f 2019-04-06)`).
|
||||||
|
* You need [WireGuard](https://www.wireguard.com) *and* the `wg` CLI in the path. The tool will call `wg show all dump` and of course will fail if the `wg` executable is not found. If you want I can add the option of specifying the `wg` path in the command line, just open an issue for it.
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
To compile the latest master version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/MindFlavor/prometheus_wireguard_exporter.git
|
||||||
|
cd prometheus_wireguard_exporter
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want the latest release you can simply use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install prometheus_wireguard_exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Start the binary with `-h` to get the complete syntax. The parameters are:
|
||||||
|
|
||||||
|
| Parameter | Mandatory | Valid values | Default | Description |
|
||||||
|
| -- | -- | -- | -- | -- |
|
||||||
|
| `-v` | no | <switch> | | Enable verbose mode.
|
||||||
|
| `-p` | no | any valid port number | 9576 | Specify the service port. This is the port your Prometheus instance should point to.
|
||||||
|
|
||||||
|
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` (or whichever port you choose).
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Prometheus WireGuard Exporter
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/prometheus_wireguard_exporter
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
4
example.json
Normal file
4
example.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
{ "path": "/home/mindflavor", "recursive": true },
|
||||||
|
{ "path": "/home/mindflavor/.cargo", "recursive": false }
|
||||||
|
]
|
BIN
extra/00.png
Normal file
BIN
extra/00.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 134 KiB |
69
src/exporter_error.rs
Normal file
69
src/exporter_error.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub(crate) enum ExporterError {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[fail(display = "Generic error")]
|
||||||
|
Generic {},
|
||||||
|
|
||||||
|
#[fail(display = "Hyper error: {}", e)]
|
||||||
|
Hyper { e: hyper::error::Error },
|
||||||
|
|
||||||
|
#[fail(display = "http error: {}", e)]
|
||||||
|
Http { e: http::Error },
|
||||||
|
|
||||||
|
#[fail(display = "UTF-8 error: {}", e)]
|
||||||
|
UTF8 { e: std::string::FromUtf8Error },
|
||||||
|
|
||||||
|
#[fail(display = "JSON format error: {}", e)]
|
||||||
|
JSON { e: serde_json::error::Error },
|
||||||
|
|
||||||
|
#[fail(display = "IO Error: {}", e)]
|
||||||
|
IO { e: std::io::Error },
|
||||||
|
|
||||||
|
#[fail(display = "UTF8 conversion error: {}", e)]
|
||||||
|
Utf8 { e: std::str::Utf8Error },
|
||||||
|
|
||||||
|
#[fail(display = "int conversion error: {}", e)]
|
||||||
|
ParseInt { e: std::num::ParseIntError },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for ExporterError {
|
||||||
|
fn from(e: std::io::Error) -> Self {
|
||||||
|
ExporterError::IO { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::error::Error> for ExporterError {
|
||||||
|
fn from(e: hyper::error::Error) -> Self {
|
||||||
|
ExporterError::Hyper { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<http::Error> for ExporterError {
|
||||||
|
fn from(e: http::Error) -> Self {
|
||||||
|
ExporterError::Http { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::FromUtf8Error> for ExporterError {
|
||||||
|
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||||
|
ExporterError::UTF8 { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::error::Error> for ExporterError {
|
||||||
|
fn from(e: serde_json::error::Error) -> Self {
|
||||||
|
ExporterError::JSON { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::str::Utf8Error> for ExporterError {
|
||||||
|
fn from(e: std::str::Utf8Error) -> Self {
|
||||||
|
ExporterError::Utf8 { e }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for ExporterError {
|
||||||
|
fn from(e: std::num::ParseIntError) -> Self {
|
||||||
|
ExporterError::ParseInt { e }
|
||||||
|
}
|
||||||
|
}
|
134
src/main.rs
Normal file
134
src/main.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
extern crate serde_json;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure;
|
||||||
|
use clap;
|
||||||
|
use clap::Arg;
|
||||||
|
use futures::future::{done, ok, Either, Future};
|
||||||
|
use http::StatusCode;
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Request, Response, Server};
|
||||||
|
use log::{error, info, trace};
|
||||||
|
use std::env;
|
||||||
|
mod options;
|
||||||
|
use options::Options;
|
||||||
|
mod exporter_error;
|
||||||
|
use exporter_error::ExporterError;
|
||||||
|
mod render_to_prometheus;
|
||||||
|
use render_to_prometheus::RenderToPrometheus;
|
||||||
|
mod wireguard;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::string::String;
|
||||||
|
use wireguard::WireGuard;
|
||||||
|
|
||||||
|
fn check_compliance(req: &Request<Body>) -> Result<(), Response<Body>> {
|
||||||
|
if req.uri() != "/metrics" {
|
||||||
|
trace!("uri not allowed {}", req.uri());
|
||||||
|
Err(Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(hyper::Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
} else if req.method() != "GET" {
|
||||||
|
trace!("method not allowed {}", req.method());
|
||||||
|
Err(Response::builder()
|
||||||
|
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||||
|
.body(hyper::Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_request(
|
||||||
|
req: Request<Body>,
|
||||||
|
options: Options,
|
||||||
|
) -> impl Future<Item = Response<Body>, Error = failure::Error> {
|
||||||
|
trace!("{:?}", req);
|
||||||
|
|
||||||
|
done(check_compliance(&req)).then(move |res| match res {
|
||||||
|
Ok(_) => Either::A(perform_request(req, &options).then(|res| match res {
|
||||||
|
Ok(body) => ok(body),
|
||||||
|
Err(err) => {
|
||||||
|
error!("internal server error: {:?}", err);
|
||||||
|
ok(Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(hyper::Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
Err(err) => Either::B(ok(err)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_request(
|
||||||
|
_req: Request<Body>,
|
||||||
|
_options: &Options,
|
||||||
|
) -> impl Future<Item = Response<Body>, Error = ExporterError> {
|
||||||
|
trace!("perform_request");
|
||||||
|
|
||||||
|
done(
|
||||||
|
Command::new("wg")
|
||||||
|
.arg("show")
|
||||||
|
.arg("all")
|
||||||
|
.arg("dump")
|
||||||
|
.output(),
|
||||||
|
)
|
||||||
|
.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))
|
||||||
|
.from_err()
|
||||||
|
.and_then(|wg| ok(Response::new(Body::from(wg.render()))))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = clap::App::new("prometheus_wireguard_exporter")
|
||||||
|
.version("0.1")
|
||||||
|
.author("Francesco Cogno <francesco.cogno@outlook.com>")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("port")
|
||||||
|
.short("p")
|
||||||
|
.help("exporter port (default 9576)")
|
||||||
|
.default_value("9576")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("verbose")
|
||||||
|
.short("v")
|
||||||
|
.help("verbose logging")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let options = Options::from_claps(&matches);
|
||||||
|
|
||||||
|
if options.verbose {
|
||||||
|
env::set_var("RUST_LOG", "prometheus_wireguard_exporter=trace");
|
||||||
|
} else {
|
||||||
|
env::set_var("RUST_LOG", "prometheus_wireguard_exporter=info");
|
||||||
|
}
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
info!("using options: {:?}", options);
|
||||||
|
|
||||||
|
let bind = matches.value_of("port").unwrap();
|
||||||
|
let bind = u16::from_str_radix(&bind, 10).expect("port must be a valid number");
|
||||||
|
let addr = ([0, 0, 0, 0], bind).into();
|
||||||
|
|
||||||
|
info!("starting exporter on {}", addr);
|
||||||
|
|
||||||
|
let new_svc = move || {
|
||||||
|
let options = options.clone();
|
||||||
|
service_fn(move |req| handle_request(req, options.clone()))
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = Server::bind(&addr)
|
||||||
|
.serve(new_svc)
|
||||||
|
.map_err(|e| eprintln!("server error: {}", e));
|
||||||
|
hyper::rt::run(server);
|
||||||
|
}
|
12
src/options.rs
Normal file
12
src/options.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Options {
|
||||||
|
pub verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Options {
|
||||||
|
pub fn from_claps(matches: &clap::ArgMatches<'_>) -> Options {
|
||||||
|
Options {
|
||||||
|
verbose: matches.is_present("verbose"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/render_to_prometheus.rs
Normal file
3
src/render_to_prometheus.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub trait RenderToPrometheus {
|
||||||
|
fn render(&self) -> String;
|
||||||
|
}
|
211
src/wireguard.rs
Normal file
211
src/wireguard.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use crate::exporter_error::ExporterError;
|
||||||
|
use crate::render_to_prometheus::RenderToPrometheus;
|
||||||
|
use log::{debug, trace};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
const EMPTY: &str = "(none)";
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub(crate) struct LocalEndpoint {
|
||||||
|
pub public_key: String,
|
||||||
|
pub private_key: String,
|
||||||
|
pub local_port: u32,
|
||||||
|
pub persistent_keepalive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct RemoteEndpoint {
|
||||||
|
pub public_key: String,
|
||||||
|
pub remote_ip: Option<String>,
|
||||||
|
pub remote_port: Option<u32>,
|
||||||
|
pub local_ip: String,
|
||||||
|
pub local_subnet: String,
|
||||||
|
pub latest_handshake: SystemTime,
|
||||||
|
pub sent_bytes: u128,
|
||||||
|
pub received_bytes: u128,
|
||||||
|
pub persistent_keepalive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum Endpoint {
|
||||||
|
Local(LocalEndpoint),
|
||||||
|
Remote(RemoteEndpoint),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_option_string(s: &str) -> Option<String> {
|
||||||
|
if s == EMPTY {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bool(s: &str) -> bool {
|
||||||
|
s != "off"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct WireGuard {
|
||||||
|
pub interfaces: HashMap<String, Vec<Endpoint>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for WireGuard {
|
||||||
|
type Error = ExporterError;
|
||||||
|
|
||||||
|
fn try_from(input: &str) -> Result<Self, Self::Error> {
|
||||||
|
debug!("wireguard::try_from({}) called", input);
|
||||||
|
let mut wg = WireGuard {
|
||||||
|
interfaces: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
let v: Vec<&str> = line.split('\t').filter(|s| !s.is_empty()).collect();
|
||||||
|
debug!("v == {:?}", v);
|
||||||
|
|
||||||
|
let endpoint = if v.len() == 5 {
|
||||||
|
// this is the local interface
|
||||||
|
Endpoint::Local(LocalEndpoint {
|
||||||
|
public_key: v[1].to_owned(),
|
||||||
|
private_key: v[2].to_owned(),
|
||||||
|
local_port: v[3].parse::<u32>().unwrap(),
|
||||||
|
persistent_keepalive: to_bool(v[4]),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// remote endpoint
|
||||||
|
let public_key = v[1].to_owned();
|
||||||
|
|
||||||
|
let (remote_ip, remote_port) = if let Some(ip_and_port) = to_option_string(v[3]) {
|
||||||
|
let toks: Vec<&str> = ip_and_port.split(':').collect();
|
||||||
|
(
|
||||||
|
Some(toks[0].to_owned()),
|
||||||
|
Some(toks[1].parse::<u32>().unwrap()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let tok: Vec<&str> = v[4].split('/').collect();
|
||||||
|
let (local_ip, local_subnet) = (tok[0].to_owned(), tok[1].to_owned());
|
||||||
|
|
||||||
|
// the latest_handhshake is based on Linux representation: a tick is a second. So
|
||||||
|
// the hack here is: add N seconds to the UNIX_EPOCH constant. This wil not work
|
||||||
|
// on other platforms if the returned ticks are *not* seconds. Sadly I did not find
|
||||||
|
// an alternative way to initialize a SystemTime from a tick number.
|
||||||
|
Endpoint::Remote(RemoteEndpoint {
|
||||||
|
public_key,
|
||||||
|
remote_ip,
|
||||||
|
remote_port,
|
||||||
|
local_ip,
|
||||||
|
local_subnet,
|
||||||
|
latest_handshake: UNIX_EPOCH
|
||||||
|
.checked_add(Duration::from_secs(v[5].parse::<u64>()?))
|
||||||
|
.unwrap(),
|
||||||
|
sent_bytes: v[6].parse::<u128>().unwrap(),
|
||||||
|
received_bytes: v[7].parse::<u128>().unwrap(),
|
||||||
|
persistent_keepalive: to_bool(v[8]),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!("{:?}", endpoint);
|
||||||
|
|
||||||
|
if let Some(endpoints) = wg.interfaces.get_mut(v[0]) {
|
||||||
|
endpoints.push(endpoint);
|
||||||
|
} else {
|
||||||
|
let mut new_vec = Vec::new();
|
||||||
|
new_vec.push(endpoint);
|
||||||
|
wg.interfaces.insert(v[0].to_owned(), new_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("{:?}", wg);
|
||||||
|
Ok(wg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderToPrometheus for WireGuard {
|
||||||
|
fn render(&self) -> 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
s.push_str(
|
||||||
|
"# HELP wireguard_sent_bytes Bytes sent to the peer
|
||||||
|
# TYPE wireguard_sent_bytes counter\n",
|
||||||
|
);
|
||||||
|
for peer in sent_bytes {
|
||||||
|
s.push_str(&peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push_str(
|
||||||
|
"# HELP wireguard_received_bytes Bytes received from the peer
|
||||||
|
# TYPE wireguard_received_bytes counter\n",
|
||||||
|
);
|
||||||
|
for peer in received_bytes {
|
||||||
|
s.push_str(&peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
s.push_str(
|
||||||
|
"# HELP wireguard_latest_handshake_seconds Seconds from the last handshake
|
||||||
|
# TYPE wireguard_latest_handshake_seconds gauge\n",
|
||||||
|
);
|
||||||
|
for peer in latest_handshakes {
|
||||||
|
s.push_str(&peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("{}", s);
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const TEXT : &'static str = "wg0\t000q4qAC0ExW/BuGSmVR1nxH9JAXT6g9Wd3oEGy5lA=\t0000u8LWR682knVm350lnuqlCJzw5SNLW9Nf96P+m8=\t51820\toff
|
||||||
|
wg0\t2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=\t(none)\t37.159.76.245:29159\t10.70.0.2/32\t1555771458\t10288508\t139524160\toff
|
||||||
|
wg0\tqnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=\t(none)\t(none)\t10.70.0.3/32\t0\t0\t0\toff
|
||||||
|
wg0\tL2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=\t(none)\t(none)\t10.70.0.4/32\t0\t0\t0\toff
|
||||||
|
wg0\tMdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=\t(none)\t(none)\t10.70.0.50/32\t0\t0\t0\toff
|
||||||
|
wg2\tMdVOIPKt9K2MPj/sO2NlWQbOnFJcL/qX80mmhQwsUlA=\t(none)\t(none)\t10.70.5.50/32\t0\t0\t0\toff
|
||||||
|
pollo\tYdVOIPKt9K2MPsO2NlWQbOnFJcL/qX80mmhQwsUlA=\t(none)\t(none)\t10.70.70.50/32\t0\t0\t0\toff
|
||||||
|
wg0\t928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=\t(none)\t5.90.62.106:21741\t10.70.0.80/32\t1555344925\t283012\t6604620\toff
|
||||||
|
";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse() {
|
||||||
|
let a = WireGuard::try_from(TEXT).unwrap();
|
||||||
|
println!("{:?}", a);
|
||||||
|
assert!(a.interfaces.len() == 3);
|
||||||
|
assert!(a.interfaces["wg0"].len() == 6);
|
||||||
|
|
||||||
|
let e1 = match &a.interfaces["wg0"][1] {
|
||||||
|
Endpoint::Local(_) => panic!(),
|
||||||
|
Endpoint::Remote(re) => re,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(e1.public_key == "2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_and_serialize() {
|
||||||
|
let a = WireGuard::try_from(TEXT).unwrap();
|
||||||
|
let s = a.render();
|
||||||
|
println!("{}", s);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue