1use std::boxed::Box;
18use std::collections::HashMap;
19use std::net::{Shutdown, TcpStream};
20use std::path::PathBuf;
21use std::time::Instant;
22
23use clap::{Command, Arg, crate_authors, crate_version, crate_description};
24use rayon::prelude::*;
25use reqwest::StatusCode;
26use reqwest::blocking::{Client, Response};
27use serde::Deserialize;
28use serde_json::Value;
29
30
31pub fn arg_handler() -> Option<PathBuf> {
33 let matches = Command::new("connchk")
34 .version(crate_version!())
35 .author(crate_authors!())
36 .about(crate_description!())
37 .arg(Arg::new("config")
38 .help("Path to the configuration file to use")
39 .index(1)
40 .required(true))
41 .get_matches();
42
43 matches.get_one::<String>("config").map(PathBuf::from)
44}
45
46#[derive(Deserialize, Debug, Clone)]
49pub struct HttpOptions {
50 pub params: Option<HashMap<String,String>>,
51 pub json: Option<Value>,
52 pub ok: u16,
53}
54
55#[derive(Deserialize, Debug)]
57pub struct Resource {
58 pub desc: String,
59 pub addr: String,
60 pub custom: Option<HttpOptions>,
61 pub kind: ResType,
62 pub res: Option<String>,
63}
64
65impl Resource {
66 pub fn check(&self) -> Result<(), Box<dyn std::error::Error>> {
68 match self.kind {
69 ResType::Tcp => {
70 self.check_tcp()?;
71 },
72 ResType::Http => {
73 if let Some(opts) = &self.custom {
74 self.check_http_custom(opts)?;
75 } else {
76 self.check_http_basic()?;
77 }
78 }
79 }
80 Ok(())
81 }
82
83 fn check_http_basic(&self) -> Result<(), Box<dyn std::error::Error>> {
87 let client = Client::new();
88 let resp = client.get(&self.addr).send()?;
89 if resp.status() == StatusCode::OK {
90 Ok(())
91 } else {
92 let msg = format!("\n\tStatus: {}\n\tDetails: {}", resp.status().as_str(), resp.text()?);
93 Err(From::from(msg))
94 }
95 }
96
97 fn check_http_custom(&self, options: &HttpOptions) -> Result<(), Box<dyn std::error::Error>> {
103 let client = Client::new();
104 let resp: Response;
105 if let Some(params) = &options.params {
106 resp = client.post(&self.addr)
107 .form(params)
108 .send()?;
109 self.custom_http_resp(options, resp)?
110 } else if let Some(json) = &options.json {
111 resp = client.post(&self.addr)
112 .json(json)
113 .send()?;
114 self.custom_http_resp(options, resp)?
115 };
116
117 Ok(())
118 }
119
120 fn custom_http_resp(&self, options: &HttpOptions, resp: Response) -> Result<(), Box<dyn std::error::Error>> {
123 let resp_code = resp.status().as_u16();
124 if resp_code == options.ok {
125 Ok(())
126 } else {
127 let msg = format!("\n\tStatus: {}\n\tDetails: {}", resp.status().as_str(), resp.text()?);
128 Err(From::from(msg))
129 }
130 }
131
132 fn check_tcp(&self) -> Result<(), Box<dyn std::error::Error>> {
136 let stream = TcpStream::connect(&self.addr)?;
137 stream.shutdown(Shutdown::Both)?;
138 Ok(())
139 }
140}
141
142#[derive(Deserialize, Debug)]
144pub enum ResType {
145 Http,
147 Tcp,
149}
150
151#[derive(Deserialize, Debug)]
154pub struct NetworkResources {
155 pub target: Vec<Resource>,
156}
157
158impl NetworkResources {
159 pub fn check_resources(&mut self) {
165 self.target.par_iter_mut()
166 .for_each(|el| {
167 let now = Instant::now();
168 match el.check() {
169 Ok(_) => {
170 let dur = now.elapsed().as_millis();
171 let res = format!("Successfully connected to {} in {}ms", el.desc, dur);
172 el.res = Some(res);
173 },
174 Err(e) => {
175 let res = format!("Failed to connect to {} with: {}", el.desc, e);
176 el.res = Some(res);
177 }
178 }
179 });
180
181 for target in self.target.iter() {
182 if let Some(result) = &target.res {
183 println!("{}", result)
184 }
185 }
186 }
187}