diff --git a/Cargo.lock b/Cargo.lock index 80c13cc..2aa9919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.34" @@ -91,6 +100,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.11" @@ -125,6 +143,7 @@ version = "0.1.0" dependencies = [ "ignore-result", "log", + "random-string", "rust_mc_proto", "serde_yml", "simplelog", @@ -185,6 +204,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "random-string" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70fd13c3024ae3f17381bb5c4d409c6dc9ea6895c08fa2147aba305bea3c4af" +dependencies = [ + "fastrand", +] + [[package]] name = "rust_mc_proto" version = "0.1.16" diff --git a/Cargo.toml b/Cargo.toml index 1b1c45d..25b0f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,5 @@ rust_mc_proto = { git = "https://github.com/MeexReay/rust_mc_proto", features = uuid = "1.11.0" log = "0.4.22" simplelog = "0.12.2" -# derivative = "2.2.0" -# no_deadlocks = "1.3.2" -# tokio = {version = "1.39.3", features = ["full"] } -# async-trait = "0.1.81" -ignore-result = "0.2.0" \ No newline at end of file +ignore-result = "0.2.0" +random-string = "1.1.0" \ No newline at end of file diff --git a/README.md b/README.md index 292978f..9c1e456 100644 --- a/README.md +++ b/README.md @@ -6,5 +6,5 @@ Proxy for minecraft servers on rust todo list: (✅ / ❌) - ❌ add methods `connect_to_ip`, `connect_to_server`, `connect_to_stream`, `reconnect` - ❌ make setting `no_pf_for_ip_connect` working -- ❌ make talk server -- ❌ create bukkit plugin for player formatting support and talking +- ❌ make messaging server +- ❌ create bukkit plugin for player formatting support and messaging diff --git a/config.yml b/config.yml index f558a73..bfb7217 100644 --- a/config.yml +++ b/config.yml @@ -1,16 +1,34 @@ 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) +messaging: # messaging server (optional) + enabled: true + host: 127.0.0.1:12346 # host + secret: qwerty123456 # secret key -servers: # verified servers (name -> ip) - play: 127.0.0.1:12345 +servers: + play: # server internal name + host: 127.0.0.1:12345 # server host + domains: + - _ # means that this server is default to connect players + - play.localhost + - mc.localhost + forwarding: # player forwarding + enabled: true + type: velocity + secret: "123456" -forced_hosts: # connect to server from connected hostname (name -> hostname) (optional) - play: play.localhost +default_forwarding: # player forwarding to use when you connecting by ip + enabled: false # disable player forwarding means that you dont need to transfer player's ip and other info to this server + # type: velocity + # secret: "123456" -default_server: play # default server to connect (optional) +incoming_forwarding: # player forwarding for incoming connections + enabled: false + # type: velocity + # secret: "123456" -player_forwarding: # how to transfer player ip to connected server (velocity:PASSWORD / bungeecord / bungeeguard:PASSWORD / none) - _: none # default player forwarding - play: velocity:123456 # specific player forwarding \ No newline at end of file +# player forwarding types: +# - velocity (or "modern" in Velocity config) (secret is required) +# - bungeecord (or "legacy" in Velocity config) (secret is optional) +# - meexprox (best) (secret is required) +# - none (enabled: false) \ No newline at end of file diff --git a/src/meexprox/config.rs b/src/meexprox/config.rs index 66bfa85..a261a78 100644 --- a/src/meexprox/config.rs +++ b/src/meexprox/config.rs @@ -1,33 +1,36 @@ -use serde_yml::Value; +use serde_yml::{Mapping, Value}; use std::fs; use std::path::Path; -use super::error::ProxyError; - #[derive(Clone, Debug)] pub struct ServerInfo { pub name: String, pub host: String, - pub forced_host: Option, + pub domains: Vec, pub player_forwarding: PlayerForwarding, } impl ServerInfo { - pub fn new(name: String, host: String, forced_host: Option, player_forwarding: PlayerForwarding) -> ServerInfo { + pub fn new( + name: String, + host: String, + domains: Vec, + player_forwarding: PlayerForwarding + ) -> ServerInfo { ServerInfo { name, host, - forced_host, + domains, player_forwarding } } - pub fn from_host(host: String, config: ProxyConfig) -> ServerInfo { + pub fn from_host(host: String, player_forwarding: PlayerForwarding) -> ServerInfo { ServerInfo { - name: host.clone(), + name: String::new(), host, - forced_host: None, - player_forwarding: config.default_player_forwarding.clone() + domains: Vec::new(), + player_forwarding } } } @@ -35,111 +38,136 @@ impl ServerInfo { #[derive(Clone, Debug)] pub enum PlayerForwarding { Velocity(String), - Bungeecord, - Bungeeguard(String), - None, + Bungeecord(Option), + Meexprox(String), + None } impl PlayerForwarding { - pub fn parse(name: &str) -> Result { - match name { - "bungeecord" => Ok(PlayerForwarding::Bungeecord), - "none" => Ok(PlayerForwarding::None), - pf => { - if pf.starts_with("bungeeguard:") { - Ok(PlayerForwarding::Bungeeguard(pf[9..].to_string())) - } else if pf.starts_with("velocity:") { - Ok(PlayerForwarding::Velocity(pf[9..].to_string())) - } else { - Err(ProxyError::ConfigParse) + pub fn from_data(data: Mapping) -> Option { + if data.len() == 0 { return None } + Some(if data.get("enabled")?.as_bool()? { + match data.get("type")?.as_str()? { + "velocity" => { + PlayerForwarding::Velocity( + data.get("secret")? + .as_str()? + .to_string() + ) + }, "bungeecord" => { + PlayerForwarding::Bungeecord( + data.get("secret") + .map(|o| o.as_str()) + .flatten() + .map(|o| o.to_string()) + ) + }, "meexprox" => { + PlayerForwarding::Meexprox( + data.get("secret")? + .as_str()? + .to_string() + ) + }, _ => { + return None; } - }, - } + } + } else { + PlayerForwarding::None + }) } } +#[derive(Clone)] +pub struct Messaging { + pub host: String, + pub secret: String +} + #[derive(Clone)] pub struct ProxyConfig { pub host: String, pub servers: Vec, - pub default_server: Option, - pub talk_host: Option, - pub talk_secret: Option, - pub default_player_forwarding: PlayerForwarding, + pub messaging: Option, + pub default_forwarding: PlayerForwarding, + pub incoming_forwarding: PlayerForwarding } impl ProxyConfig { pub fn new( host: String, servers: Vec, - default_server: Option, - talk_host: Option, - talk_secret: Option, - default_player_forwarding: PlayerForwarding + messaging: Option, + default_forwarding: PlayerForwarding, + incoming_forwarding: PlayerForwarding ) -> ProxyConfig { ProxyConfig { host, servers, - default_server, - talk_host, - talk_secret, - default_player_forwarding + messaging, + default_forwarding, + incoming_forwarding } } - pub fn load_yml(data: String) -> Result> { - let data = serde_yml::from_str::(&data)?; - let data = data.as_mapping().ok_or(ProxyError::ConfigParse)?; + pub fn load_yml(data: String) -> Option { + let data = serde_yml::from_str::(&data).ok()?; + let data = data.as_mapping()?; - let host = data.get("host").map(|o| o.as_str()).flatten().ok_or(ProxyError::ConfigParse)?.to_string(); - let talk_host = data.get("talk_host").map(|o| o.as_str()).flatten().map(|o| o.to_string()); - let talk_secret = data.get("talk_secret").map(|o| o.as_str()).flatten().map(|o| o.to_string()); - let player_forwarding = data.get("player_forwarding").ok_or(ProxyError::ConfigParse)?.as_mapping().ok_or(ProxyError::ConfigParse)?.clone(); - let default_player_forwarding = PlayerForwarding::parse(player_forwarding["_"].as_str().ok_or(ProxyError::ConfigParse)?)?; + let host = data.get("host")?.as_str()?.to_string(); + + let messaging = if let Some(map) = data.get("messaging") { + let map = map.as_mapping()?; - 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(ServerInfo::new(name.clone(), addr.clone(), None, - player_forwarding.get(name).map(|o| o.as_str()).flatten() - .map(PlayerForwarding::parse).ok_or(ProxyError::ConfigParse)??)); - } + if map.get("enabled")?.as_bool()? { + Some(Messaging { + host: map.get("host")?.as_str()?.to_string(), + secret: map.get("secret")?.as_str()?.to_string(), + }) + } else { + None } - } + } else { + 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 servers: Vec = data.get("servers")?.as_mapping()? + .iter() + .filter_map(|o| -> Option { + let map = o.1.as_mapping()?; + Some(ServerInfo::new( + o.0.as_str()?.to_string(), + map.get("host")?.as_str()?.to_string(), + map.get("domains")?.as_sequence()? + .iter() + .filter_map(|o| o.as_str()) + .map(|o| o.to_string()) + .collect(), + PlayerForwarding::from_data( + map.get("forwarding")?.as_mapping()?.clone() + )? + )) + }) + .collect(); - let default_server = data.get("default_server") - .map(|o| o.as_str()).flatten() - .and_then(|ds| servers.iter().find(|s| s.name == ds).cloned()); + let default_forwarding = PlayerForwarding::from_data( + data.get("default_forwarding")?.as_mapping()?.clone() + )?; - Ok(ProxyConfig::new( + let incoming_forwarding = PlayerForwarding::from_data( + data.get("incoming_forwarding")?.as_mapping()?.clone() + )?; + + Some(ProxyConfig::new( host, servers, - default_server, - talk_host, - talk_secret, - default_player_forwarding, + messaging, + default_forwarding, + incoming_forwarding )) } - pub fn load(path: impl AsRef) -> Result> { - Self::load_yml(fs::read_to_string(path)?) + pub fn load(path: impl AsRef) -> Option { + Self::load_yml(fs::read_to_string(path).ok()?) } pub fn get_server_by_name(&self, name: &str) -> Option { @@ -151,14 +179,19 @@ impl ProxyConfig { None } - pub fn get_server_by_forced_host(&self, forced_host: &str) -> Option { + pub fn get_server_by_domain(&self, domain: &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()); - } + if server.domains.contains(&domain.to_string()) { + return Some(server.clone()); } } + + for server in &self.servers { + if server.domains.contains(&"_".to_string()) { + return Some(server.clone()); + } + } + None } } diff --git a/src/meexprox/connection.rs b/src/meexprox/connection.rs index 79ffaf1..6a4a5a6 100644 --- a/src/meexprox/connection.rs +++ b/src/meexprox/connection.rs @@ -1,7 +1,7 @@ -use std::{net::TcpStream, sync::{Arc, Mutex}, thread}; +use std::{net::{SocketAddr, TcpStream}, sync::{Arc, Mutex}, thread}; use ignore_result::Ignore; -use rust_mc_proto::{DataBufferReader, MCConnTcp, Packet}; +use rust_mc_proto::{DataBufferReader, DataBufferWriter, MCConnTcp, Packet, ProtocolError}; use uuid::Uuid; use super::{config::{ProxyConfig, ServerInfo}, error::{AsProxyResult, ProxyError}}; @@ -18,8 +18,45 @@ pub struct LoginInfo { } impl LoginInfo { - pub fn write(&self, config: &ProxyConfig, stream: &mut MCConnTcp) { - todo!() // TODO: write login packets sending + pub fn write(&self, _config: &ProxyConfig, stream: &mut MCConnTcp) -> Result<(), ProtocolError> { + stream.write_packet(&Packet::build(0x00, |p| { + p.write_u16_varint(self.protocol_version)?; + p.write_string(&self.server_address)?; + p.write_short(self.server_port as i16)?; + p.write_u8_varint(2) + })?)?; + + stream.write_packet(&Packet::build(0x00, |p| { + p.write_string(&self.name)?; + p.write_uuid(&self.uuid) + })?)?; + + loop { + let mut packet = stream.read_packet()?; + + match packet.id() { + 0x01 => { + stream.write_packet(&Packet::build(0x00, |p| { + p.write_usize_varint(self.shared_secret.as_ref().unwrap().len())?; + p.write_bytes(&self.shared_secret.as_ref().unwrap())?; + p.write_usize_varint(self.verify_token.as_ref().unwrap().len())?; + p.write_bytes(&self.verify_token.as_ref().unwrap()) + })?)?; + } + 0x02 => { + break; + } + 0x03 => { + let compression = Some(packet.read_usize_varint()?); + stream.set_compression(compression); + } + _ => {} + } + } + + stream.write_packet(&Packet::empty(0x03))?; + + Ok(()) } } @@ -30,7 +67,8 @@ pub struct Player { pub name: String, pub uuid: Uuid, pub server: Option, - pub protocol_version: u16 + pub protocol_version: u16, + pub addr: SocketAddr } impl Player { @@ -39,6 +77,7 @@ impl Player { server_address: String, server_port: u16, server: ServerInfo, + addr: SocketAddr, mut client_conn: MCConnTcp, mut server_conn: MCConnTcp ) -> Result { @@ -52,6 +91,7 @@ impl Player { server_conn.write_packet(&packet).as_proxy()?; let mut player = Player { + addr, client_conn: Arc::new(Mutex::new(client_conn)), server_conn: Arc::new(Mutex::new(server_conn)), login_info: None, diff --git a/src/meexprox/meexprox.rs b/src/meexprox/meexprox.rs index a8acc13..21c443b 100644 --- a/src/meexprox/meexprox.rs +++ b/src/meexprox/meexprox.rs @@ -1,5 +1,4 @@ use log::{error, info}; -// use no_deadlocks::Mutex; use rust_mc_proto::{ read_packet, write_packet, DataBufferReader, DataBufferWriter, MCConnTcp, Packet }; @@ -64,8 +63,7 @@ impl MeexProx { let next_state = handshake.read_u8_varint().as_proxy()?; let server = self.config - .get_server_by_forced_host(&server_address) - .or(self.config.default_server.clone()) + .get_server_by_domain(&server_address) .ok_or(ProxyError::ConfigParse)?; let mut server_conn = TcpStream::connect(&server.host).map_err(|_| ProxyError::ServerConnect)?; @@ -76,18 +74,6 @@ impl MeexProx { handshake.write_unsigned_short(server_port)?; handshake.write_u8_varint(next_state)?; - if let PlayerForwarding::Handshake = self.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(()) }).as_proxy()?; @@ -107,6 +93,7 @@ impl MeexProx { server_address, server_port, server, + addr, client_conn, server_conn )?);