diff --git a/Cargo.toml b/Cargo.toml index cdc7e75..9b3ceaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,13 @@ description = "check mumble server stats using http requests" #license = "LICENSE" [dependencies] +axum = "0.7.4" chrono = "0.4" clap = { version = "4.5", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } tokio = { version = "1.36", features = ["net", "macros", "rt-multi-thread", "io-util"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index d5f96e3..e417055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,68 +1,47 @@ -use std::net::SocketAddr; +use std::net::ToSocketAddrs; use clap::Parser; -use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::UdpSocket}; + +use axum::{extract::Query, routing::get, Json, Router}; +use mumble::ping_mumble_server; +use proto::PongPacket; + +mod proto; +mod mumble; #[derive(Parser)] struct CliArgs { - server: String, } #[tokio::main] async fn main() { - let args = CliArgs::parse(); + // initialize tracing + tracing_subscriber::fmt::init(); - let dest : SocketAddr = args.server.parse().unwrap(); + // build our application with a route + let app = Router::new() + .route("/ping", get(ping_server)); - // by default bind on any interface and request OS to give us a port - let mut addr : SocketAddr = "0.0.0.0:0".parse().unwrap(); - let socket = UdpSocket::bind(addr).await.unwrap(); - - - let packet = PingPacket { id: 0, time: chrono::Utc::now().timestamp_micros() as u64 }; - let buf = packet.serialize().await.unwrap(); - - socket.send_to(&buf, dest).await.unwrap(); - - let mut buf = [0u8; 1024]; - socket.recv(&mut buf).await.unwrap(); - - let pong = PongPacket::deserialize(&buf).await.unwrap(); - - println!("{pong:#?}"); + let listener = tokio::net::TcpListener::bind("127.0.0.1:57039").await.unwrap(); + axum::serve(listener, app).await.unwrap(); } -struct PingPacket { - id: i32, - time: u64, +#[derive(serde::Deserialize)] +struct PingOptions { + host: String, + port: Option, } -impl PingPacket { - async fn serialize(self) -> std::io::Result> { - let mut out = Vec::new(); - out.write_i32(self.id).await?; - out.write_u64(self.time).await?; - Ok(out) - } -} - - -#[derive(Debug)] -struct PongPacket { - version: u32, - time: u64, - users: i32, - max_users: i32, - bandwidth: i32, -} - -impl PongPacket { - async fn deserialize(mut data: &[u8]) -> std::io::Result { - let version = data.read_u32().await?; - let time = data.read_u64().await?; - let users = data.read_i32().await?; - let max_users = data.read_i32().await?; - let bandwidth = data.read_i32().await?; - Ok(PongPacket { version, time, users, max_users, bandwidth }) +async fn ping_server(Query(options): Query) -> Result, String> { + let tuple = (options.host, options.port.unwrap_or(64738)); + match tuple.to_socket_addrs() { + Err(e) => Err(format!("invalid address: {e}")), + Ok(mut addrs) => match addrs.next() { + None => Err("could not resolve host".to_string()), + Some(addr) => match ping_mumble_server(addr).await { + Ok(pong) => Ok(Json(pong)), + Err(e) => Err(format!("could not ping server: {e}")), + } + } } } diff --git a/src/mumble.rs b/src/mumble.rs new file mode 100644 index 0000000..e9f6e3a --- /dev/null +++ b/src/mumble.rs @@ -0,0 +1,22 @@ +use std::net::SocketAddr; + +use tokio::net::UdpSocket; + +use crate::proto::{PingPacket, PongPacket}; + + +pub async fn ping_mumble_server(addr: SocketAddr) -> std::io::Result { + // by default bind on any interface and request OS to give us a port + let from_addr : SocketAddr = "0.0.0.0:0".parse().expect("could not create socketaddr from '0.0.0.0:0'"); + let socket = UdpSocket::bind(from_addr).await?; + + let packet = PingPacket { id: 0, time: chrono::Utc::now().timestamp_micros() as u64 }; + let buf = packet.serialize().await?; + + socket.send_to(&buf, addr).await?; + + let mut buf = [0u8; 64]; + socket.recv(&mut buf).await?; + + PongPacket::deserialize(&buf).await +} diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..f191996 --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,35 @@ +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +pub struct PingPacket { + pub id: i32, + pub time: u64, +} + +impl PingPacket { + pub async fn serialize(self) -> std::io::Result> { + let mut out = Vec::new(); + out.write_i32(self.id).await?; + out.write_u64(self.time).await?; + Ok(out) + } +} + +#[derive(Debug, serde::Serialize)] +pub struct PongPacket { + pub version: u32, + pub time: u64, + pub users: i32, + pub max_users: i32, + pub bandwidth: i32, +} + +impl PongPacket { + pub async fn deserialize(mut data: &[u8]) -> std::io::Result { + let version = data.read_u32().await?; + let time = data.read_u64().await?; + let users = data.read_i32().await?; + let max_users = data.read_i32().await?; + let bandwidth = data.read_i32().await?; + Ok(PongPacket { version, time, users, max_users, bandwidth }) + } +}