diff --git a/Cargo.toml b/Cargo.toml index e8aafa6..1099795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio = { version = "1.38", features = ["rt","net", "macros"] } +tokio = { version = "1.38", features = ["rt", "net", "macros"] } hickory-client = "0.24.1" hickory-proto = "0.24.1" env_logger = "0.11.3" diff --git a/src/main.rs b/src/main.rs index 6d51390..9322602 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use env_logger::Builder; use log::LevelFilter; use services::http; use services::https; +use services::minecraft; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -11,9 +12,11 @@ async fn main() -> std::io::Result<()> { let http_listener = tokio::spawn(http::listener(80)); let https_listener = tokio::spawn(https::listener(443)); + let minecraft_listener = tokio::spawn(minecraft::listener(25565)); let _ = http_listener.await.expect("http_listener failed"); _ = https_listener.await.expect("https_listener failed"); + _ = minecraft_listener.await.expect("minecraft_listener failed"); Ok(()) } diff --git a/src/services/http.rs b/src/services/http.rs index 68a919b..a925e9f 100644 --- a/src/services/http.rs +++ b/src/services/http.rs @@ -1,6 +1,4 @@ -use tokio::io; use tokio::net::{TcpListener, TcpStream}; - use crate::utils::{get_bind_address, resolve_addr}; async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { @@ -19,7 +17,7 @@ async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { loop { if let Some(host_string) = host.clone() { - let resolved_address: Result = + let resolved_address: Result = resolve_addr(&host_string).await; if let Ok(ip) = resolved_address { log::info!( @@ -29,7 +27,7 @@ async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { ip ); - let server: Result = + let server: Result = TcpStream::connect(format!("[{}]:{}", ip, port)).await; if server.is_err() { log::error!( @@ -48,8 +46,8 @@ async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { src_addr, format!("[{}]:{}", ip, port) ); - tokio::spawn(async move { io::copy(&mut eread, &mut owrite).await }); - tokio::spawn(async move { io::copy(&mut oread, &mut ewrite).await }); + tokio::spawn(async move { tokio::io::copy(&mut eread, &mut owrite).await }); + tokio::spawn(async move { tokio::io::copy(&mut oread, &mut ewrite).await }); return Some(()); } else { if buf.len() > 4096 || last_buf_read_len == 0 { diff --git a/src/services/https.rs b/src/services/https.rs index 92ecc04..d2920df 100644 --- a/src/services/https.rs +++ b/src/services/https.rs @@ -1,6 +1,5 @@ -use tokio::io; -use rustls::server::{Accepted, Acceptor}; use tokio::net::{TcpListener, TcpStream}; +use rustls::server::{Accepted, Acceptor}; use crate::utils::{get_bind_address, resolve_addr}; pub async fn get_sni_from_packet(packet: Vec) -> Option { @@ -48,7 +47,7 @@ pub async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { loop { if let Some(sni_string) = get_sni_from_packet(buf.clone()).await { - let resolved_address: Result = + let resolved_address: Result = resolve_addr(&sni_string).await; if let Ok(ip) = resolved_address { log::info!( @@ -58,7 +57,7 @@ pub async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { ip ); - let server: Result = + let server: Result = TcpStream::connect(format!("[{}]:{}", ip, port)).await; if server.is_err() { log::error!( @@ -77,8 +76,8 @@ pub async fn handle_connection(client: TcpStream, port: u16) -> Option<()> { src_addr, format!("[{}]:{}", ip, port) ); - tokio::spawn(async move { io::copy(&mut eread, &mut owrite).await }); - tokio::spawn(async move { io::copy(&mut oread, &mut ewrite).await }); + tokio::spawn(async move { tokio::io::copy(&mut eread, &mut owrite).await }); + tokio::spawn(async move { tokio::io::copy(&mut oread, &mut ewrite).await }); return Some(()); } else { log::error!( diff --git a/src/services/minecraft.rs b/src/services/minecraft.rs new file mode 100644 index 0000000..089a29e --- /dev/null +++ b/src/services/minecraft.rs @@ -0,0 +1,134 @@ +use tokio::net::{TcpListener, TcpStream}; +use crate::utils::{get_bind_address, resolve_addr}; + +struct MinecraftServer { + hostname: String, + port: u16, + protocol_version: i32, +} + +impl MinecraftServer { + fn read_server_info(packet: &[u8]) -> Result { + // Read packet ID + if packet[1] != 0x00 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unexpected packet ID", + )); + } + + // Read protocol version + let protocol_version: i32 = Self::read_var_int(&packet[2..4])?; + + // Read hostname length + let hostname_length = packet[4]; + + println!("{:?}", hostname_length); + + // Read hostname + let hostname = + String::from_utf8_lossy(&packet[5..5 + hostname_length as usize]).to_string(); + + // Read port + let port = (packet[5 + hostname_length as usize] as u16) << 8 | packet[6 + hostname_length as usize] as u16; + + Ok(MinecraftServer { + hostname, + port, + protocol_version, + }) + } + + fn read_var_int(mut packet: &[u8]) -> Result { + let mut result: i32 = 0; + let mut position: i32 = 0; + + println!("{:?}", packet); + + loop { + if position > 35 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "VarInt is too big", + )); + } + + let byte = packet[0] as i32; + packet = &packet[1..]; + + result |= (byte & 0b0111_1111) << position; + position += 7; + + if (byte & 0b1000_0000) == 0 { + break; + } + } + + Ok(result) + } +} + +// implement debug for MinecraftServer +impl std::fmt::Debug for MinecraftServer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MinecraftServer") + .field("hostname", &self.hostname) + .field("port", &self.port) + .field("protocol_version", &self.protocol_version) + .finish() + } +} + +async fn handle_connection(client: TcpStream, _port: u16) -> Option<()> { + let src_addr = client.peer_addr().ok()?; + + // read request header and get the host + let mut buf: Vec = vec![0; 256]; + client.peek(&mut buf).await.expect("peek failed"); + + let server_info = MinecraftServer::read_server_info(&buf).expect("Failed to read server info"); + + log::info!( + "Minecraft {} Trying connect to: {}", + src_addr, + server_info.hostname + ); + + let ip = resolve_addr(&server_info.hostname).await.expect("Failed to resolve hostname"); + let upstream = format!("{}:{}", ip, server_info.port); + let server: Result = + TcpStream::connect(upstream.clone()).await; + if server.is_err() { + log::error!( + "Minecraft {} Failed to connect to upstream: {}", + src_addr, + upstream + ); + return None; + } + + let server: TcpStream = server.ok()?; + let (mut eread, mut ewrite) = client.into_split(); + let (mut oread, mut owrite) = server.into_split(); + log::info!( + "Minecraft {} Connected to upstream: {}", + src_addr, + upstream + ); + tokio::spawn(async move { tokio::io::copy(&mut eread, &mut owrite).await }); + tokio::spawn(async move { tokio::io::copy(&mut oread, &mut ewrite).await }); + None +} + +pub async fn listener(port: u16) -> std::io::Result<()> { + let listener: TcpListener = + TcpListener::bind(format!("{}:{}", get_bind_address(), port)).await?; + log::info!("Listening on {}", listener.local_addr()?); + + loop { + let (client, _) = listener.accept().await?; + tokio::spawn(async move { + handle_connection(client, port).await; + }); + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs index 18e7a9f..d45081d 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,2 +1,3 @@ pub mod http; pub mod https; +pub mod minecraft;