commit - 9919a59522a2e5d6fff449a0db34a65ea3739fa0
commit + 8e4d062a47b7003064cb9ff22232e4e9b804571b
blob - df99c69198f5813df5fc3eaa007a2af0e60a7bbd
blob + ffaf97ed9dd4c2c3e5388126b167e66e9e946c6a
--- .rustfmt.toml
+++ .rustfmt.toml
max_width = 80
+imports_granularity = "Crate"
blob - b1c6e1b68dee3acc61a1caa530b96c67464d8796
blob + ef5baa7e0c6d3d1cbb5673ba4f4a8e308cb42714
--- asp.rs
+++ asp.rs
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
-use std::env;
-use std::ffi::{CStr, CString};
-use std::fs;
-use std::io::{self, Read, Write};
-use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
-use std::os::raw::{c_char, c_int};
-use std::os::unix::io::AsRawFd;
-use std::process::Command;
-use std::thread;
-use std::time::Duration;
+use std::{
+ env,
+ ffi::{CStr, CString},
+ fs,
+ io::{self, Read, Write},
+ net::{SocketAddr, TcpStream, ToSocketAddrs},
+ os::unix::io::AsRawFd,
+ process::Command,
+ thread,
+ time::Duration,
+};
const VERSION: &str = env!("ASP_VERSION");
-const TLS_WANT_POLLIN: isize = -2;
-const TLS_WANT_POLLOUT: isize = -3;
const HEAD: &str = r#"<!DOCTYPE html>
<html lang="en">
<div class="container">
"#;
-// libtls(3) FFI
+use ffi::*;
-enum Tls {}
-enum TlsCfg {}
+mod ffi {
+ use std::os::raw::{c_char, c_int};
-unsafe extern "C" {
- fn tls_init() -> c_int;
- fn tls_config_new() -> *mut TlsCfg;
- fn tls_config_free(cfg: *mut TlsCfg);
- fn tls_client() -> *mut Tls;
- fn tls_configure(
- ctx: *mut Tls,
- cfg: *mut TlsCfg,
- ) -> c_int;
- fn tls_connect_socket(
- ctx: *mut Tls,
- fd: c_int,
- name: *const c_char,
- ) -> c_int;
- fn tls_read(
- ctx: *mut Tls,
- buf: *mut u8,
- len: usize,
- ) -> isize;
- fn tls_write(
- ctx: *mut Tls,
- buf: *const u8,
- len: usize,
- ) -> isize;
- fn tls_close(ctx: *mut Tls) -> c_int;
- fn tls_free(ctx: *mut Tls);
- fn tls_error(ctx: *mut Tls) -> *const c_char;
+ pub enum Tls {}
+ pub enum TlsCfg {}
+
+ pub const TLS_WANT_POLLIN: isize = -2;
+ pub const TLS_WANT_POLLOUT: isize = -3;
+
+ unsafe extern "C" {
+ pub fn tls_init() -> c_int;
+ pub fn tls_config_new() -> *mut TlsCfg;
+ pub fn tls_config_free(cfg: *mut TlsCfg);
+ pub fn tls_client() -> *mut Tls;
+ pub fn tls_configure(
+ ctx: *mut Tls,
+ cfg: *mut TlsCfg,
+ ) -> c_int;
+ pub fn tls_connect_socket(
+ ctx: *mut Tls,
+ fd: c_int,
+ name: *const c_char,
+ ) -> c_int;
+ pub fn tls_read(
+ ctx: *mut Tls,
+ buf: *mut u8,
+ len: usize,
+ ) -> isize;
+ pub fn tls_write(
+ ctx: *mut Tls,
+ buf: *const u8,
+ len: usize,
+ ) -> isize;
+ pub fn tls_close(ctx: *mut Tls) -> c_int;
+ pub fn tls_free(ctx: *mut Tls);
+ pub fn tls_error(ctx: *mut Tls) -> *const c_char;
+ }
}
struct Check {
!t.is_empty() && !t.starts_with('#')
})
.filter_map(|line| {
- let c: Vec<&str> =
- line.splitn(4, ',').map(|s| s.trim()).collect();
+ let c: Vec<&str> = line.splitn(4, ',').map(|s| s.trim()).collect();
if c.len() < 4 {
return None;
}
}
}
-fn resolve(
- host: &str,
- port: u16,
- v: &str,
-) -> io::Result<SocketAddr> {
- let addrs: Vec<SocketAddr> =
- (host, port).to_socket_addrs()?.collect();
+fn resolve(host: &str, port: u16, v: &str) -> io::Result<SocketAddr> {
+ let addrs: Vec<SocketAddr> = (host, port).to_socket_addrs()?.collect();
let addr = match v {
"4" => addrs.iter().find(|a| a.is_ipv4()),
"6" => addrs.iter().find(|a| a.is_ipv6()),
_ => addrs.first(),
};
addr.copied().ok_or_else(|| {
- io::Error::new(
- io::ErrorKind::AddrNotAvailable,
- "no address",
- )
+ io::Error::new(io::ErrorKind::AddrNotAvailable, "no address")
})
}
let (host, port) = if hp.starts_with('[') {
let end = hp.find(']')?;
let h = &hp[1..end];
- let p =
- if hp.len() > end + 1 && hp.as_bytes()[end + 1] == b':'
- {
- hp[end + 2..].parse().ok()?
- } else if scheme == "https" {
- 443
- } else {
- 80
- };
+ let p = if hp.len() > end + 1 && hp.as_bytes()[end + 1] == b':' {
+ hp[end + 2..].parse().ok()?
+ } else if scheme == "https" {
+ 443
+ } else {
+ 80
+ };
(h, p)
} else {
match hp.rsplit_once(':') {
Some((h, p)) => (h, p.parse().ok()?),
None => {
- let p =
- if scheme == "https" { 443 } else { 80 };
+ let p = if scheme == "https" { 443 } else { 80 };
(hp, p)
}
}
if p.is_null() {
"TLS error".into()
} else {
- unsafe {
- CStr::from_ptr(p).to_string_lossy().into_owned()
- }
+ unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
}
}
Command::new("date")
.arg("+%FT%T%z")
.output()
- .map(|o| {
- String::from_utf8_lossy(&o.stdout)
- .trim()
- .to_string()
- })
+ .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default()
}
if line.len() > 9 {
let lower = line[..9].to_ascii_lowercase();
if lower == "location:" {
- location =
- Some(line[9..].trim().to_string());
+ location = Some(line[9..].trim().to_string());
}
}
}
}
fn resolve_redirect(base: &str, loc: &str) -> String {
- if loc.starts_with("http://")
- || loc.starts_with("https://")
- {
+ if loc.starts_with("http://") || loc.starts_with("https://") {
return loc.to_string();
}
if loc.starts_with("//") {
- let scheme =
- base.split("://").next().unwrap_or("https");
+ let scheme = base.split("://").next().unwrap_or("https");
return format!("{}:{}", scheme, loc);
}
if let Some((scheme, host, port, _)) = parse_url(base) {
v: &str,
timeout: Duration,
) -> Result<(i32, Option<String>), String> {
- let (scheme, host, port, path) =
- parse_url(url).ok_or("invalid URL")?;
- let addr =
- resolve(host, port, v).map_err(|e| e.to_string())?;
+ let (scheme, host, port, path) = parse_url(url).ok_or("invalid URL")?;
+ let addr = resolve(host, port, v).map_err(|e| e.to_string())?;
let stream = TcpStream::connect_timeout(&addr, timeout)
.map_err(|e| e.to_string())?;
let _ = stream.set_read_timeout(Some(timeout));
) -> (bool, String) {
let mut url = url.to_string();
for _ in 0..5 {
- let (code, loc) = match http_get(&url, v, timeout)
- {
+ let (code, loc) = match http_get(&url, v, timeout) {
Ok(r) => r,
Err(e) => return (false, e),
};
url = resolve_redirect(&url, &l);
}
_ => {
- let info = format!(
- "status {}, expected {}",
- code, expected
- );
+ let info = format!("status {}, expected {}", code, expected);
return (code == expected, info);
}
}
(false, "too many redirects".into())
}
-fn http_req(
- stream: &TcpStream,
- req: &str,
-) -> Result<String, String> {
+fn http_req(stream: &TcpStream, req: &str) -> Result<String, String> {
let mut s = stream;
- s.write_all(req.as_bytes())
- .map_err(|e| e.to_string())?;
+ s.write_all(req.as_bytes()).map_err(|e| e.to_string())?;
let mut buf = [0u8; 4096];
let mut total = 0;
loop {
- let n = s
- .read(&mut buf[total..])
- .map_err(|e| e.to_string())?;
+ let n = s.read(&mut buf[total..]).map_err(|e| e.to_string())?;
if n == 0 {
break;
}
total += n;
- if total >= 4
- && buf[..total]
- .windows(4)
- .any(|w| w == b"\r\n\r\n")
- {
+ if total >= 4 && buf[..total].windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
if total >= buf.len() {
}
tls_config_free(cfg);
- let name = CString::new(host)
- .map_err(|_| "invalid host")?;
- if tls_connect_socket(
- ctx,
- stream.as_raw_fd(),
- name.as_ptr(),
- ) != 0
- {
+ let name = CString::new(host).map_err(|_| "invalid host")?;
+ if tls_connect_socket(ctx, stream.as_raw_fd(), name.as_ptr()) != 0 {
let e = tls_err(ctx);
tls_close(ctx);
tls_free(ctx);
let bytes = req.as_bytes();
let mut off = 0;
while off < bytes.len() {
- let n = tls_write(
- ctx,
- bytes[off..].as_ptr(),
- bytes.len() - off,
- );
+ let n = tls_write(ctx, bytes[off..].as_ptr(), bytes.len() - off);
if n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT {
continue;
}
let mut buf = [0u8; 4096];
let mut total = 0;
loop {
- let n = tls_read(
- ctx,
- buf[total..].as_mut_ptr(),
- buf.len() - total,
- );
+ let n = tls_read(ctx, buf[total..].as_mut_ptr(), buf.len() - total);
if n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT {
continue;
}
break;
}
total += n as usize;
- if total >= 4
- && buf[..total]
- .windows(4)
- .any(|w| w == b"\r\n\r\n")
- {
+ if total >= 4 && buf[..total].windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
if total >= buf.len() {
if total == 0 {
return Err("empty response".into());
}
- Ok(String::from_utf8_lossy(&buf[..total])
- .into_owned())
+ Ok(String::from_utf8_lossy(&buf[..total]).into_owned())
}
}
}
}
-fn run_checks(
- checks: &[Check],
- timeout: u64,
-) -> Vec<CheckResult> {
+fn run_checks(checks: &[Check], timeout: u64) -> Vec<CheckResult> {
let dur = Duration::from_secs(timeout);
thread::scope(|s| {
let handles: Vec<_> = checks
};
}
let v = ipv(&c.kind);
- let (ok, info) =
- if c.kind.starts_with("http") {
- check_http(
- &c.host, c.expected, v, dur,
- )
- } else if c.kind.starts_with("ping") {
- check_ping(
- &c.host, c.expected, v,
- timeout,
- )
- } else if c.kind.starts_with("port") {
- check_port(
- &c.host, c.expected, v, dur,
- )
- } else {
- (
- false,
- format!(
- "unknown: {}",
- c.kind
- ),
- )
- };
+ let (ok, info) = if c.kind.starts_with("http") {
+ check_http(&c.host, c.expected, v, dur)
+ } else if c.kind.starts_with("ping") {
+ check_ping(&c.host, c.expected, v, timeout)
+ } else if c.kind.starts_with("port") {
+ check_port(&c.host, c.expected, v, dur)
+ } else {
+ (false, format!("unknown: {}", c.kind))
+ };
CheckResult {
name: c.name.clone(),
ok,
})
})
.collect();
- handles
- .into_iter()
- .map(|h| h.join().unwrap())
- .collect()
+ handles.into_iter().map(|h| h.join().unwrap()).collect()
})
}
fn render_html(results: &[CheckResult], incidents: &str) {
- let outages = results
- .iter()
- .filter(|r| !r.group && !r.ok)
- .count();
+ let outages = results.iter().filter(|r| !r.group && !r.ok).count();
print!("{}", HEAD);
println!("<h1>Status</h1>");
println!("<h1>Services</h1>\n<ul>");
for r in results {
if r.group {
- println!(
- "<li class=\"panel group\">{}</li>",
- esc(&r.name)
- );
+ println!("<li class=\"panel group\">{}</li>", esc(&r.name));
} else if r.ok {
println!(
"<li>{} <span class=\"status success\">\
}
}
+ println!("<p class=\"small info\">Last check: {}</p>", now());
println!(
- "<p class=\"small info\">Last check: {}</p>",
- now()
- );
- println!(
"<p class=\"small info\">Powered by \
<a href=\"https://git.ijanc.org/asp\">asp</a></p>"
);
}
fn main() {
- let (checks_path, incidents_path, timeout) =
- match parse_args() {
- Some(a) => a,
- None => {
- usage();
- std::process::exit(1);
- }
- };
+ let (checks_path, incidents_path, timeout) = match parse_args() {
+ Some(a) => a,
+ None => {
+ usage();
+ std::process::exit(1);
+ }
+ };
unsafe {
if tls_init() != 0 {
let checks = parse_checks(&checks_path);
let results = run_checks(&checks, timeout);
- let incidents =
- fs::read_to_string(&incidents_path).unwrap_or_default();
+ let incidents = fs::read_to_string(&incidents_path).unwrap_or_default();
render_html(&results, &incidents);
}