diff --git a/src/dns.rs b/src/dns.rs index 8cfb46c..82ab7d4 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -7,7 +7,7 @@ const RECURSION_DESIRED: u16 = 1 << 8; const RECORD_TYPE_SRV: u16 = 33; const RECORD_CLASS_IN: u16 = 1; -pub fn create_dns_query(qname: &String, qtype: u16, qclass: u16) -> Result> { +pub fn create_dns_query(qname: &str, qtype: u16, qclass: u16) -> Result> { let qname = match qname.is_ascii() { true => qname, false => return Err(Error::new(ErrorKind::InvalidInput, "domain is not valid ASCII")), @@ -110,7 +110,7 @@ pub fn parse_srv_response(mut ptr: usize, recv: &[u8]) -> Option pub fn resolve_srv_port(domain: &String) -> Option { let request = create_dns_query( - &("_minecraft._tcp.".to_string() + domain), + &format!("_minecraft._tcp.{}", domain), RECORD_TYPE_SRV, RECORD_CLASS_IN).unwrap(); diff --git a/src/main.rs b/src/main.rs index fddb15f..beb9d9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,8 +34,10 @@ env!("CARGO_PKG_VERSION")); std::process::exit(0); } + let verbose = args.contains(&"-v".to_string()); + if args[1] != "serve" { - let mut address = String::from(args[1].as_str()); + let mut address = String::from(args[args.len() - 1].as_str()); if !address.contains(":") { let port: u16 = match resolve_srv_port(&address) { Some(port) => port, @@ -45,21 +47,50 @@ env!("CARGO_PKG_VERSION")); address.push_str(":"); address.push_str(port.to_string().as_str()); } - let mut addrs_iter = address.to_socket_addrs().unwrap(); - let address = addrs_iter.next().unwrap(); + let addrs_iter = match address.to_socket_addrs() { + Ok(addr) => addr, + Err(e) => { + panic!("Failed to parse address {}: {}", address, e) + } + }; - let status = MinecraftStatus::fetch(address).unwrap(); + let mut error: Option = None; - println!("Version: {} ({})", status.version.name, status.version.protocol); - println!("Players: {}/{}", status.players.online, status.players.max); - println!( - "Enforces Secure Chat: {}", - if status.enforces_secure_chat() { "true" } else { "false" }, - ); - println!("MOTD:"); - println!("{}", status.parse_description()); + if verbose { + println!( + "{} address{} available.", + addrs_iter.len(), + // horrible no good code but it's really funny to look at + // i'm sure this is awesome if you're into functional programming + (addrs_iter.len() == 1).then(|| "es").unwrap_or("")) + } - return Ok(()); + for address in addrs_iter { + let status = match MinecraftStatus::fetch(address, verbose) { + Ok(status) => status, + Err (e) => { + error = Some(Error::new( + std::io::ErrorKind::Other, + format!("Failed to fetch status: {}", e))); + continue + } + }; + + println!("Version: {} ({})", status.version.name, status.version.protocol); + println!("Players: {}/{}", status.players.online, status.players.max); + println!( + "Enforces Secure Chat: {}", + if status.enforces_secure_chat() { "true" } else { "false" }, + ); + println!("MOTD:"); + println!("{}", status.parse_description()); + + return Ok(()); + } + + if let Some(error) = error { + return Err(error); + } } let mut address = "0.0.0.0:8080".to_string(); @@ -135,7 +166,7 @@ env!("CARGO_PKG_VERSION")); Ok(mut addrs_iter) => { let address = addrs_iter.next().unwrap(); - match MinecraftStatus::fetch(address) { + match MinecraftStatus::fetch(address, false) { Err(_) => { response.status(StatusCode::InternalServerError); query_response = format!( @@ -219,7 +250,7 @@ env!("CARGO_PKG_VERSION")); Ok(mut addrs_iter) => { let address = addrs_iter.next().unwrap(); - match MinecraftStatus::fetch(address) { + match MinecraftStatus::fetch(address, false) { Err(_) => { response.status(StatusCode::InternalServerError); response.body(format!("Failed to connect to {address}.\n")); diff --git a/src/status.rs b/src/status.rs index 7d28f96..f62a2ae 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,8 +1,11 @@ use std::io::{Error, ErrorKind, Read, Write, Result}; use std::net::{SocketAddr, TcpStream}; +use std::time::Duration; use crate::leb128::{read_leb128, write_leb128}; +const TIMEOUT_SECS: u64 = 10; + #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftVersion { pub name: String, @@ -55,17 +58,18 @@ pub struct MinecraftStatus { } impl MinecraftStatus { - pub fn fetch(address: SocketAddr) -> Result { - // println!("Connecting to {address}..."); + pub fn fetch(address: SocketAddr, verbose: bool) -> Result { + if verbose { println!("Connecting to {address}..."); } - let stream = TcpStream::connect(address.to_string()); + let stream = TcpStream::connect_timeout( + &address, Duration::from_secs(TIMEOUT_SECS)); if stream.is_err() { return Err(stream.unwrap_err()); } - // println!("Connected!"); + if verbose { println!("Connected!"); } let mut stream = stream.unwrap(); let mut send_buffer: Vec = Vec::new(); - // println!("Sending payload..."); + if verbose { println!("Sending payload..."); } send_buffer.push(0x00); write_leb128(&mut send_buffer, 769); // 1.21.4 write_leb128(&mut send_buffer, address.ip().to_string().len().try_into().unwrap()); @@ -76,6 +80,8 @@ impl MinecraftStatus { send_packet(&mut stream, &[0x00]).unwrap(); + if verbose { println!("Awaiting response..."); } + let mut data: Vec = Vec::new(); let mut len: usize = 0; let mut msg_len: usize = 0;