Skip to content

Commit

Permalink
add Minecraft protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
xlmnxp committed Aug 27, 2024
1 parent ec09018 commit 6466ccb
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ use env_logger::Builder;
use log::LevelFilter;
use services::http;
use services::https;
use services::minecraft;

#[tokio::main]
async fn main() -> std::io::Result<()> {
Builder::new().filter(None, LevelFilter::Info).init();

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(())
}
10 changes: 4 additions & 6 deletions src/services/http.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand All @@ -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<std::net::IpAddr, io::Error> =
let resolved_address: Result<std::net::IpAddr, tokio::io::Error> =
resolve_addr(&host_string).await;
if let Ok(ip) = resolved_address {
log::info!(
Expand All @@ -29,7 +27,7 @@ async fn handle_connection(client: TcpStream, port: u16) -> Option<()> {
ip
);

let server: Result<TcpStream, io::Error> =
let server: Result<TcpStream, tokio::io::Error> =
TcpStream::connect(format!("[{}]:{}", ip, port)).await;
if server.is_err() {
log::error!(
Expand All @@ -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 {
Expand Down
11 changes: 5 additions & 6 deletions src/services/https.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) -> Option<String> {
Expand Down Expand Up @@ -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<std::net::IpAddr, io::Error> =
let resolved_address: Result<std::net::IpAddr, tokio::io::Error> =
resolve_addr(&sni_string).await;
if let Ok(ip) = resolved_address {
log::info!(
Expand All @@ -58,7 +57,7 @@ pub async fn handle_connection(client: TcpStream, port: u16) -> Option<()> {
ip
);

let server: Result<TcpStream, io::Error> =
let server: Result<TcpStream, tokio::io::Error> =
TcpStream::connect(format!("[{}]:{}", ip, port)).await;
if server.is_err() {
log::error!(
Expand All @@ -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!(
Expand Down
134 changes: 134 additions & 0 deletions src/services/minecraft.rs
Original file line number Diff line number Diff line change
@@ -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<MinecraftServer, std::io::Error> {
// 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<i32, std::io::Error> {
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<u8> = 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, tokio::io::Error> =
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;
});
}
}
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod http;
pub mod https;
pub mod minecraft;

0 comments on commit 6466ccb

Please sign in to comment.