diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..26f4617 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,445 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bytebuffer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7bfaf7cd08cacd74cdc6b521c37ac39cbc92692e5ab5c21ed5657a749b577c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libyml" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e281a65eeba3d4503a2839252f86374528f9ceafe6fed97c1d3b52e1fb625c1" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "meexprox" +version = "0.1.0" +dependencies = [ + "log", + "rust_mc_proto", + "serde_yml", + "simplelog", + "uuid", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust_mc_proto" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5392814ae7129d56b0afcbdd5d6d4764ca51273fab7ff604ebe73018dbb562" +dependencies = [ + "bytebuffer", + "flate2", + "uuid", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yml" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ce6afeda22f0b55dde2c34897bce76a629587348480384231205c14b59a01f" +dependencies = [ + "indexmap", + "itoa", + "libyml", + "log", + "memchr", + "ryu", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[package]] +name = "syn" +version = "2.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..27ed727 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "meexprox" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde_yml = "0.0.10" +rust_mc_proto = "0.1.15" +uuid = "1.10.0" +log = "0.4.22" +simplelog = "0.12.2" diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..0324931 --- /dev/null +++ b/config.yml @@ -0,0 +1,15 @@ +host: 127.0.0.1:25565 # host to bind meexprox + +talk_host: 127.0.0.1:12346 # secret host to talk with meexprox (optional) +talk_secret: qwerty123456 # secret token for talk with meexprox (optional) + +servers: # verified servers (name -> ip) + play: 127.0.0.1:12345 + +forced_hosts: # connect to server from connected hostname (name -> hostname) (optional) + play: play.localhost + +default_server: play # default server to connect (optional) + +player_forwarding: disabled # how to transfer player ip to connected server (handshake / plugin response / disabled) +no_pf_for_ip_connect: true # disable player forwarding for connecting with server ip \ No newline at end of file diff --git a/latest.log b/latest.log new file mode 100644 index 0000000..18f1562 --- /dev/null +++ b/latest.log @@ -0,0 +1,5 @@ +23:50:29 [INFO] meexprox started on 127.0.0.1:25565 +23:50:49 [ERROR] clientbound error +23:50:49 [ERROR] serverbound error +23:51:05 [ERROR] clientbound error +23:51:05 [ERROR] serverbound error diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0ba013c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,439 @@ +use log::{debug, error, info}; +use rust_mc_proto::{ + DataBufferReader, DataBufferWriter, MinecraftConnection, Packet, ProtocolError, Zigzag, +}; +use serde_yml::Value; +use std::{ + error::Error, + fs, + net::{SocketAddr, TcpListener, TcpStream}, + sync::{Arc, Mutex}, + thread, +}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct ProxyServer { + pub name: String, + pub host: String, + pub forced_host: Option, +} + +impl ProxyServer { + pub fn new(name: String, host: String, forced_host: Option) -> ProxyServer { + ProxyServer { + name, + host, + forced_host, + } + } +} + +#[derive(Debug)] +pub enum ProxyError { + ConfigParse, + ServerConnect, +} + +impl std::fmt::Display for ProxyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({:?})", self) + } +} + +impl std::error::Error for ProxyError {} + +macro_rules! extract_string { + ($data:expr, $key:expr) => { + match $data.get(&Value::String($key.to_string())) { + Some(Value::String(val)) => Some(val.clone()), + _ => None, + } + }; +} + +#[derive(Clone)] +pub enum PlayerForwarding { + Handshake, + PluginResponse, + Disabled, +} + +#[derive(Clone)] +pub struct ProxyConfig { + pub host: String, + pub servers: Vec, + pub default_server: Option, + pub talk_host: Option, + pub talk_secret: Option, + pub player_forwarding: PlayerForwarding, +} + +impl ProxyConfig { + pub fn new( + host: String, + servers: Vec, + default_server: Option, + talk_host: Option, + talk_secret: Option, + player_forwarding: PlayerForwarding, + ) -> ProxyConfig { + ProxyConfig { + host, + servers, + default_server, + talk_host, + talk_secret, + player_forwarding, + } + } + + pub fn load(path: &str) -> Result> { + let data = serde_yml::from_str::(&fs::read_to_string(path)?)?; + let data = data.as_mapping().ok_or(ProxyError::ConfigParse)?; + + let host = extract_string!(data, "host").ok_or(ProxyError::ConfigParse)?; + let talk_host = extract_string!(data, "talk_host"); + let talk_secret = extract_string!(data, "talk_secret"); + let player_forwarding = match extract_string!(data, "player_forwarding") { + Some(pf) => match pf.as_str() { + "plugin response" => PlayerForwarding::PluginResponse, + "disabled" => PlayerForwarding::Disabled, + _ => PlayerForwarding::Handshake, + }, + _ => PlayerForwarding::Handshake, + }; + + let mut servers = Vec::new(); + if let Some(servers_map) = data + .get(&Value::String("servers".to_string())) + .and_then(Value::as_mapping) + { + for (name, addr) in servers_map { + if let (Value::String(name), Value::String(addr)) = (name, addr) { + servers.push(ProxyServer::new(name.clone(), addr.clone(), None)); + } + } + } + + if let Some(forced_hosts_map) = data + .get(&Value::String("forced_hosts".to_string())) + .and_then(Value::as_mapping) + { + for (name, host) in forced_hosts_map { + if let (Value::String(name), Value::String(host)) = (name, host) { + if let Some(server) = servers.iter_mut().find(|s| s.name == *name) { + server.forced_host = Some(host.clone()); + } + } + } + } + + let default_server = extract_string!(data, "default_server") + .and_then(|ds| servers.iter().find(|s| s.name == ds).cloned()); + + Ok(ProxyConfig::new( + host, + servers, + default_server, + talk_host, + talk_secret, + player_forwarding, + )) + } + + pub fn get_server_by_name(&self, name: &str) -> Option { + for server in &self.servers { + if &server.name == name { + return Some(server.clone()); + } + } + None + } + + pub fn get_server_by_forced_host(&self, forced_host: &str) -> Option { + for server in &self.servers { + if let Some(server_forced_host) = &server.forced_host { + if server_forced_host == forced_host { + return Some(server.clone()); + } + } + } + None + } + + pub fn get_host(&self) -> &str { + &self.host + } + + pub fn get_talk_enabled(&self) -> bool { + self.talk_host.is_some() && self.talk_secret.is_some() + } + + pub fn get_talk_host(&self) -> Option<&String> { + self.talk_host.as_ref() + } + + pub fn get_default_server(&self) -> Option { + self.default_server.clone() + } + + pub fn get_talk_secret(&self) -> Option<&String> { + self.talk_secret.as_ref() + } +} + +pub struct ProxyPlayer { + pub connection: TcpStream, + pub connection_server: TcpStream, + pub name: Option, + pub uuid: Option, + pub server: Option, +} + +impl ProxyPlayer { + pub fn new( + connection: TcpStream, + connection_server: TcpStream, + name: Option, + uuid: Option, + server: Option, + ) -> ProxyPlayer { + ProxyPlayer { + connection, + connection_server, + name, + uuid, + server, + } + } +} + +pub struct MeexProx { + pub config: ProxyConfig, + pub players: Vec, + pub listener: Option, +} + +impl MeexProx { + pub fn new(config: ProxyConfig) -> MeexProx { + MeexProx { + config, + players: Vec::new(), + listener: None, + } + } + + pub fn get_player(&self, uuid: Uuid) -> Option<&ProxyPlayer> { + for player in &self.players { + if let Some(player_uuid) = player.uuid { + if player_uuid == uuid { + return Some(player); + } + } + } + None + } + + pub fn accept(this: Arc>, stream: TcpStream) -> Result<(), Box> { + let Ok(addr) = stream.peer_addr() else { + return Ok(()); + }; + + let server_config = this.lock().unwrap().config.clone(); + + let mut client_conn = MinecraftConnection::new(stream); + + // TODO: remove this anti-ipv6 mrakobesie!! + let SocketAddr::V4(addrv4) = addr else { + return Ok(()); + }; + debug!( + "accepted stream {}.{}.{}.{}:{}", + addrv4.ip().octets()[0], + addrv4.ip().octets()[1], + addrv4.ip().octets()[2], + addrv4.ip().octets()[3], + addrv4.port() + ); + // TODO: remove this anti-ipv6 mrakobesie!! + + let mut handshake = client_conn.read_packet()?; + + if handshake.id() != 0x00 { + return Ok(()); + } + + let protocol_version = handshake.read_u16_varint()?; + let server_address = handshake.read_string()?; + let server_port = handshake.read_unsigned_short()?; + let next_state = handshake.read_u8_varint()?; + + let server = server_config + .get_server_by_forced_host(&server_address) + .or(server_config.get_default_server()) + .ok_or(ProxyError::ConfigParse)?; + + let mut server_conn = MinecraftConnection::connect(&server.host)?; + + let handshake = Packet::build(0x00, |handshake| { + handshake.write_u16_varint(protocol_version)?; + handshake.write_string(&server_address)?; + handshake.write_unsigned_short(server_port)?; + handshake.write_u8_varint(next_state)?; + + if let PlayerForwarding::Handshake = server_config.player_forwarding { + if let SocketAddr::V4(addr) = addr { + handshake.write_boolean(false)?; // is ipv6 + handshake.write_unsigned_short(addr.port())?; // port + handshake.write_bytes(&addr.ip().octets())?; // octets + } else if let SocketAddr::V6(addr) = addr { + handshake.write_boolean(true)?; + handshake.write_unsigned_short(addr.port())?; + handshake.write_bytes(&addr.ip().octets())?; + } + } + + Ok(()) + })?; + + server_conn.write_packet(&handshake)?; + + if next_state == 1 { + debug!("state: motd"); + + loop { + server_conn.write_packet(&client_conn.read_packet()?)?; + client_conn.write_packet(&server_conn.read_packet()?)?; + } + } else if next_state == 2 { + debug!("state: login"); + + let plugin_response_packet = Packet::build(0x02, |packet| { + packet.write_i8_varint(-99)?; + packet.write_boolean(true)?; + + if let SocketAddr::V4(addr) = addr { + packet.write_boolean(false)?; // is ipv6 + packet.write_unsigned_short(addr.port())?; // port + packet.write_bytes(&addr.ip().octets())?; // octets + } else if let SocketAddr::V6(addr) = addr { + packet.write_boolean(true)?; + packet.write_unsigned_short(addr.port())?; + packet.write_bytes(&addr.ip().octets())?; + } + + Ok(()) + })?; + + thread::spawn({ + let mut client_conn = client_conn.try_clone().unwrap(); + let mut server_conn = server_conn.try_clone().unwrap(); + + move || { + move || -> Result<(), ProtocolError> { + let mut joined = false; + + loop { + let mut packet = match client_conn.read_packet() { + Ok(packet) => packet, + Err(_) => break, + }; + + if packet.id() == 0x00 && !joined { + let name = packet.read_string()?; + let uuid = packet.read_uuid()?; + + this.lock().unwrap().players.push(ProxyPlayer::new( + client_conn.get_ref().try_clone().unwrap(), + server_conn.get_ref().try_clone().unwrap(), + Some(name), + Some(uuid), + Some(server.clone()), + )); + + joined = true; + } + + // debug!("[C->S] sending packet {:#04X?} (size: {})", packet.id(), packet.len()); + server_conn.write_packet(&packet)?; + } + error!("serverbound error"); + + Ok(()) + }() + .or_else(|e| { + error!("serverbound error: {:?}", e); + Ok::<(), ()>(()) + }) + .unwrap(); + } + }); + + move || -> Result<(), ProtocolError> { + loop { + let mut packet = match server_conn.read_packet() { + Ok(packet) => packet, + Err(_) => break, + }; + + if packet.id() == 0x02 { + if let PlayerForwarding::PluginResponse = server_config.player_forwarding { + debug!( + "[C->S] sending packet {:#04X?} (size: {})", + plugin_response_packet.id(), + plugin_response_packet.len() + ); + server_conn.write_packet(&plugin_response_packet)?; + } + } + + // debug!("[C<-S] sending packet {:#04X?} (size: {})", packet.id(), packet.len()); + + client_conn.write_packet(&packet)?; + + if packet.id() == 0x03 { + let threshold = packet.read_isize_varint()?; + + if threshold >= 0 { + let threshold = threshold.zigzag(); + + server_conn.set_compression(Some(threshold)); + client_conn.set_compression(Some(threshold)); + } else { + server_conn.set_compression(None); + client_conn.set_compression(None); + } + } + } + error!("clientbound error"); + + Ok(()) + }() + .or_else(|e| { + error!("clientbound error: {:?}", e); + Ok::<(), ()>(()) + }) + .unwrap(); + } + + Ok(()) + } + + pub fn start(self) { + let listener = TcpListener::bind(self.config.get_host()).expect("invalid host"); + + info!("meexprox started on {}", self.config.get_host()); + + let mutex_self = Arc::new(Mutex::new(self)); + + for client in listener.incoming() { + if let Ok(client) = client { + let mutex_self_clone = mutex_self.clone(); + thread::spawn(move || { + Self::accept(mutex_self_clone, client).expect("accept error"); + }); + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9d1abf7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,16 @@ +use meexprox::{MeexProx, ProxyConfig}; +use simplelog::{CombinedLogger, TermLogger, Config, LevelFilter, TerminalMode, ColorChoice, WriteLogger}; +use std::fs::File; + +fn main() { + CombinedLogger::init( + vec![ + TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto), + WriteLogger::new(LevelFilter::Info, Config::default(), File::create("latest.log").unwrap()), + ] + ).unwrap(); + + let config = ProxyConfig::load("config.yml").expect("config parse error"); + let meexprox = MeexProx::new(config); + meexprox.start(); +}