2019-04-23 23:06:35 +02:00
extern crate serde_json ;
#[ macro_use ]
extern crate failure ;
2019-06-09 20:09:06 +02:00
use clap ::{ crate_authors , crate_name , crate_version , Arg } ;
2019-12-01 19:42:47 +01:00
use hyper ::{ Body , Request } ;
2019-10-13 19:42:44 +02:00
use log ::{ debug , info , trace } ;
2019-04-23 23:06:35 +02:00
use std ::env ;
mod options ;
use options ::Options ;
mod wireguard ;
use std ::convert ::TryFrom ;
use std ::process ::Command ;
use std ::string ::String ;
use wireguard ::WireGuard ;
2019-06-09 20:09:06 +02:00
mod exporter_error ;
2019-05-15 16:55:07 +02:00
mod wireguard_config ;
2019-05-20 10:09:02 +02:00
use wireguard_config ::peer_entry_hashmap_try_from ;
2019-06-09 20:09:06 +02:00
extern crate prometheus_exporter_base ;
use prometheus_exporter_base ::render_prometheus ;
2020-03-24 09:36:13 +01:00
use std ::net ::IpAddr ;
2019-09-27 11:01:27 +02:00
use std ::sync ::Arc ;
2019-04-23 23:06:35 +02:00
2019-12-01 19:42:47 +01:00
async fn perform_request (
2019-04-23 23:06:35 +02:00
_req : Request < Body > ,
2019-12-01 19:42:47 +01:00
options : Arc < Options > ,
) -> Result < String , failure ::Error > {
2020-05-09 16:48:52 +02:00
let interfaces_to_handle = match & options . interfaces {
Some ( interfaces_str ) = > interfaces_str . clone ( ) ,
None = > vec! [ " all " . to_owned ( ) ] ,
} ;
2019-05-17 19:32:35 +02:00
2020-05-09 16:48:52 +02:00
let peer_entry_contents =
if let Some ( extract_names_config_file ) = & options . extract_names_config_file {
Some ( ::std ::fs ::read_to_string (
& extract_names_config_file as & str ,
) ? )
} else {
None
} ;
2019-12-01 19:42:47 +01:00
2020-05-09 16:48:52 +02:00
let peer_entry_hashmap = if let Some ( peer_entry_contents ) = & peer_entry_contents {
Some ( peer_entry_hashmap_try_from ( peer_entry_contents ) ? )
2019-12-01 19:42:47 +01:00
} else {
2020-05-09 16:48:52 +02:00
None
2019-12-01 19:42:47 +01:00
} ;
2020-05-09 16:48:52 +02:00
let mut wg_accumulator : Option < WireGuard > = None ;
for interface_to_handle in interfaces_to_handle {
let output = Command ::new ( " wg " )
. arg ( " show " )
. arg ( & interface_to_handle )
. arg ( " dump " )
. output ( ) ? ;
let output_stdout_str = String ::from_utf8 ( output . stdout ) ? ;
trace! (
" wg show {} dump stdout == {} " ,
interface_to_handle ,
output_stdout_str
) ;
let output_stderr_str = String ::from_utf8 ( output . stderr ) ? ;
trace! (
" wg show {} dump stderr == {} " ,
interface_to_handle ,
output_stderr_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_stdout_str = if interface_to_handle ! = " all " {
debug! ( " injecting {} to the wg show output " , interface_to_handle ) ;
let mut result = String ::new ( ) ;
for s in output_stdout_str . lines ( ) {
result . push_str ( & format! ( " {} \t {} \n " , interface_to_handle , s ) ) ;
}
result
} else {
output_stdout_str
} ;
if let Some ( wg_accumulator ) = & mut wg_accumulator {
let wg = WireGuard ::try_from ( & output_stdout_str as & str ) ? ;
wg_accumulator . merge ( & wg ) ;
} else {
wg_accumulator = Some ( WireGuard ::try_from ( & output_stdout_str as & str ) ? ) ;
} ;
}
if let Some ( wg_accumulator ) = wg_accumulator {
Ok ( wg_accumulator . render_with_names (
peer_entry_hashmap . as_ref ( ) ,
2019-12-01 19:42:47 +01:00
options . separate_allowed_ips ,
options . export_remote_ip_and_port ,
) )
2020-05-09 16:48:52 +02:00
} else {
panic! ( ) ;
2019-12-01 19:42:47 +01:00
}
2019-04-23 23:06:35 +02:00
}
2019-12-01 19:42:47 +01:00
#[ tokio::main ]
async fn main ( ) {
2019-06-09 20:09:06 +02:00
let matches = clap ::App ::new ( crate_name! ( ) )
. version ( crate_version! ( ) )
. author ( crate_authors! ( " \n " ) )
2019-09-26 23:44:48 +02:00
. arg (
Arg ::with_name ( " addr " )
. short ( " l " )
. help ( " exporter address " )
2019-09-27 11:01:27 +02:00
. default_value ( " 0.0.0.0 " )
2019-09-26 23:44:48 +02:00
. takes_value ( true ) ,
)
2019-04-23 23:06:35 +02:00
. arg (
Arg ::with_name ( " port " )
. short ( " p " )
2019-05-17 19:32:35 +02:00
. help ( " exporter port " )
2019-05-20 11:23:28 +02:00
. default_value ( " 9586 " )
2019-04-23 23:06:35 +02:00
. takes_value ( true ) ,
)
. arg (
Arg ::with_name ( " verbose " )
. short ( " v " )
. help ( " verbose logging " )
. takes_value ( false ) ,
)
2019-05-17 19:32:35 +02:00
. arg (
2019-07-11 15:31:25 +02:00
Arg ::with_name ( " separate_allowed_ips " )
. short ( " s " )
. help ( " separate allowed ips and ports " )
. takes_value ( false ) ,
)
2019-07-31 15:24:52 +02:00
. arg (
Arg ::with_name ( " export_remote_ip_and_port " )
. short ( " r " )
. help ( " exports peer's remote ip and port as labels (if available) " )
. takes_value ( false ) ,
)
2019-12-01 19:42:47 +01:00
. arg (
2020-05-09 16:48:52 +02:00
Arg ::with_name ( " extract_names_config_files " )
2019-05-17 19:32:35 +02:00
. 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) " )
2020-05-09 16:48:52 +02:00
. multiple ( false )
. number_of_values ( 1 )
2019-05-17 19:32:35 +02:00
. takes_value ( true ) )
2020-04-07 10:20:19 +02:00
. arg (
2020-05-09 16:48:52 +02:00
Arg ::with_name ( " interfaces " )
2020-04-07 10:20:19 +02:00
. short ( " i " )
2020-05-09 16:48:52 +02:00
. help ( " If set specifies the interface passed to the wg show command. It is relative to the same position config_file. In not specified, all will be passed. " )
. multiple ( true )
. number_of_values ( 1 )
2020-04-07 10:20:19 +02:00
. takes_value ( true ) )
2019-04-23 23:06:35 +02:00
. get_matches ( ) ;
let options = Options ::from_claps ( & matches ) ;
if options . verbose {
2019-06-09 20:09:06 +02:00
env ::set_var (
" RUST_LOG " ,
format! ( " {} =trace,prometheus_exporter_base=trace " , crate_name! ( ) ) ,
) ;
2019-04-23 23:06:35 +02:00
} else {
2019-06-09 20:09:06 +02:00
env ::set_var (
" RUST_LOG " ,
format! ( " {} =info,prometheus_exporter_base=info " , crate_name! ( ) ) ,
) ;
2019-04-23 23:06:35 +02:00
}
env_logger ::init ( ) ;
2020-05-10 15:40:45 +02:00
info! (
" {} v{} starting... " ,
env! ( " CARGO_PKG_NAME " ) ,
env! ( " CARGO_PKG_VERSION " )
) ;
2019-04-23 23:06:35 +02:00
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 " ) ;
2020-03-24 09:36:13 +01:00
let ip = matches . value_of ( " addr " ) . unwrap ( ) . parse ::< IpAddr > ( ) . unwrap ( ) ;
2019-09-26 23:44:48 +02:00
let addr = ( ip , bind ) . into ( ) ;
2019-04-23 23:06:35 +02:00
2019-09-26 23:44:48 +02:00
info! ( " starting exporter on http://{}/metrics " , addr ) ;
2019-04-23 23:06:35 +02:00
2019-12-01 19:42:47 +01:00
render_prometheus ( addr , options , | request , options | {
Box ::pin ( perform_request ( request , options ) )
} )
. await ;
2019-04-23 23:06:35 +02:00
}