Add join forward method to proxy to other address while server starts

This commit is contained in:
timvisee 2021-11-15 18:03:03 +01:00
parent 32cd9ffc73
commit db99289ea7
No known key found for this signature in database
GPG Key ID: B8DB720BC383E172
5 changed files with 96 additions and 27 deletions

View File

@ -39,6 +39,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
- Configure joining client occupation methods: - Configure joining client occupation methods:
- Hold: hold clients when server starts, relay when ready, without them noticing - Hold: hold clients when server starts, relay when ready, without them noticing
- Kick: kick clients when server starts, with a starting message - Kick: kick clients when server starts, with a starting message
- Forward: forward client to another IP when server starts
- Customizable MOTD and login messages - Customizable MOTD and login messages
- Automatically manages `server.properties` (host, port and RCON settings) - Automatically manages `server.properties` (host, port and RCON settings)
- Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix) - Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix)

View File

@ -87,6 +87,16 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
# Keep below Minecraft timeout of 30 seconds. # Keep below Minecraft timeout of 30 seconds.
#timeout = 25 #timeout = 25
[join.forward]
# Forward occupation method.
# Instantly forwards (proxies) the client to a different address.
# You may need to configure target server for it, such as allowing proxies.
# Consumes client, not allowing other join methods afterwards.
# IP and port to forward to.
# Forwarded-to server will receive original client handshake and login request as received by lazymc.
#address = "127.0.0.1:25565"
[lockout] [lockout]
# Enable to prevent everybody from connecting through lazymc. Instantly kicks player. # Enable to prevent everybody from connecting through lazymc. Instantly kicks player.
#enabled = false #enabled = false

View File

@ -230,8 +230,14 @@ impl Default for Motd {
#[derive(Debug, Deserialize, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Deserialize, Copy, Clone, Eq, PartialEq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Method { pub enum Method {
Hold, /// Kick client with message.
Kick, Kick,
/// Hold client connection until server is ready.
Hold,
/// Forward connection to another host.
Forward,
} }
/// Join configuration. /// Join configuration.
@ -248,6 +254,10 @@ pub struct Join {
/// Join hold configuration. /// Join hold configuration.
#[serde(default)] #[serde(default)]
pub hold: JoinHold, pub hold: JoinHold,
/// Join forward configuration.
#[serde(default)]
pub forward: JoinForward,
} }
impl Default for Join { impl Default for Join {
@ -256,6 +266,7 @@ impl Default for Join {
methods: vec![Method::Hold, Method::Kick], methods: vec![Method::Hold, Method::Kick],
kick: Default::default(), kick: Default::default(),
hold: Default::default(), hold: Default::default(),
forward: Default::default(),
} }
} }
} }
@ -294,6 +305,22 @@ impl Default for JoinHold {
} }
} }
/// Join forward configuration.
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct JoinForward {
/// IP and port to forward to.
pub address: SocketAddr,
}
impl Default for JoinForward {
fn default() -> Self {
Self {
address: "127.0.0.1:25565".parse().unwrap(),
}
}
}
/// Lockout configuration. /// Lockout configuration.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(default)] #[serde(default)]

View File

@ -1,3 +1,4 @@
use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use bytes::BytesMut; use bytes::BytesMut;
@ -103,9 +104,15 @@ fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
/// Route inbound TCP stream to proxy with queued data, spawning a new task. /// Route inbound TCP stream to proxy with queued data, spawning a new task.
#[inline] #[inline]
pub fn route_proxy_queue(inbound: TcpStream, config: Arc<Config>, queue: BytesMut) { pub fn route_proxy_queue(inbound: TcpStream, config: Arc<Config>, queue: BytesMut) {
route_proxy_address_queue(inbound, config.server.address, queue);
}
/// Route inbound TCP stream to proxy with given address and queued data, spawning a new task.
#[inline]
pub fn route_proxy_address_queue(inbound: TcpStream, addr: SocketAddr, queue: BytesMut) {
// When server is online, proxy all // When server is online, proxy all
let service = async move { let service = async move {
proxy::proxy_with_queue(inbound, config.server.address, &queue) proxy::proxy_with_queue(inbound, addr, &queue)
.map(|r| { .map(|r| {
if let Err(err) = r { if let Err(err) = r {
warn!(target: "lazymc", "Failed to proxy: {}", err); warn!(target: "lazymc", "Failed to proxy: {}", err);

View File

@ -31,10 +31,13 @@ pub async fn serve(
) -> Result<(), ()> { ) -> Result<(), ()> {
let (mut reader, mut writer) = inbound.split(); let (mut reader, mut writer) = inbound.split();
// Incoming buffer and packet holding queue // Incoming buffer
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
let may_hold = config.join.methods.contains(&Method::Hold);
let mut hold_queue = BytesMut::new(); // Remember inbound packets, used for client holding and forwarding
let remember_inbound = config.join.methods.contains(&Method::Hold)
|| config.join.methods.contains(&Method::Forward);
let mut inbound_history = BytesMut::new();
loop { loop {
// Read packet from stream // Read packet from stream
@ -71,8 +74,8 @@ pub async fn serve(
client.set_state(new_state); client.set_state(new_state);
// If login handshake and holding is enabled, hold packets // If login handshake and holding is enabled, hold packets
if new_state == ClientState::Login && may_hold { if new_state == ClientState::Login && remember_inbound {
hold_queue.extend(raw); inbound_history.extend(raw);
} }
continue; continue;
@ -123,26 +126,6 @@ pub async fn serve(
// Use join occupy methods // Use join occupy methods
for method in &config.join.methods { for method in &config.join.methods {
match method { match method {
// Hold method, hold client connection while server starts
Method::Hold => {
trace!(target: "lazymc", "Using hold method to occupy joining client");
// Server must be starting
if server.state() != State::Starting {
continue;
}
// Hold login packet and remaining read bytes
hold_queue.extend(&raw);
hold_queue.extend(buf.split_off(0));
// Start holding
if hold(&config, &server).await? {
service::server::route_proxy_queue(inbound, config, hold_queue);
return Ok(());
}
}
// Kick method, immediately kick client // Kick method, immediately kick client
Method::Kick => { Method::Kick => {
trace!(target: "lazymc", "Using kick method to occupy joining client"); trace!(target: "lazymc", "Using kick method to occupy joining client");
@ -157,6 +140,47 @@ pub async fn serve(
kick(msg, &mut writer).await?; kick(msg, &mut writer).await?;
break; break;
} }
// Hold method, hold client connection while server starts
Method::Hold => {
trace!(target: "lazymc", "Using hold method to occupy joining client");
// Server must be starting
if server.state() != State::Starting {
continue;
}
// Hold login packet and remaining read bytes
inbound_history.extend(&raw);
inbound_history.extend(buf.split_off(0));
// Start holding
if hold(&config, &server).await? {
service::server::route_proxy_queue(inbound, config, inbound_history);
return Ok(());
}
}
// Forward method, forward client connection while server starts
Method::Forward => {
trace!(target: "lazymc", "Using forward method to occupy joining client");
// Hold login packet and remaining read bytes
inbound_history.extend(&raw);
inbound_history.extend(buf.split_off(0));
// Forward client
debug!(target: "lazymc", "Forwarding client to {:?}!", config.join.forward.address);
service::server::route_proxy_address_queue(
inbound,
config.join.forward.address,
inbound_history,
);
return Ok(());
// TODO: do not consume client here, allow other join method on fail
}
} }
} }