Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a very basic support for multi-server handling. #42

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.2.10 (2023-02-20)

- Do not report an error when server exits with status code 143

## 0.2.9 (2023-02-14)

- Fix dropping all connections when `server.drop_banned_ips` was enabled
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lazymc"
version = "0.2.9"
version = "0.2.10"
authors = ["Tim Visee <[email protected]>"]
license = "GPL-3.0"
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion res/lazymc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,4 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
[config]
# lazymc version this configuration is for.
# Don't change unless you know what you're doing.
version = "0.2.9"
version = "0.2.10"
18 changes: 9 additions & 9 deletions src/action/start.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::sync::Arc;

use clap::ArgMatches;

Expand All @@ -16,18 +15,19 @@ const RCON_PASSWORD_LENGTH: usize = 32;
pub fn invoke(matches: &ArgMatches) -> Result<(), ()> {
// Load config
#[allow(unused_mut)]
let mut config = config::load(matches);
let mut configs = config::load(matches);

// Prepare RCON if enabled
#[cfg(feature = "rcon")]
prepare_rcon(&mut config);
for config in configs.iter_mut() {
// Prepare RCON if enabled
#[cfg(feature = "rcon")]
prepare_rcon(config);

// Rewrite server server.properties file
rewrite_server_properties(&config);
// Rewrite server server.properties file
rewrite_server_properties(config);
}

// Start server service
let config = Arc::new(config);
service::server::service(config)
service::server::service(configs)
}

/// Prepare RCON.
Expand Down
42 changes: 35 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,34 @@ pub const CONFIG_FILE: &str = "lazymc.toml";
/// Configuration version user should be using, or warning will be shown.
const CONFIG_VERSION: &str = "0.2.8";

/// Load config from file, based on CLI arguments.
///
/// Quits with an error message on failure.
pub fn load(matches: &ArgMatches) -> Config {
pub fn load(matches: &ArgMatches) -> Vec<Config> {
// Get config path, attempt to canonicalize
let mut path = PathBuf::from(matches.get_one::<String>("config").unwrap());
if let Ok(p) = path.canonicalize() {
path = p;
}

// Ensure configuration file exists
if !path.is_file() {
let paths: Vec<PathBuf> = if path.is_dir() {
path.read_dir()
.unwrap()
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.collect()
} else {
vec![path.clone()]
};

let configs: Vec<Config> = paths
.into_iter()
.filter(|path| path.is_file())
.map(load_file)
.collect();

// Ensure configuration file/directory exists
if configs.is_empty() {
quit_error_msg(
format!(
"Config file does not exist: {}",
"Config file/directory does not exist: {}",
path.to_str().unwrap_or("?")
),
ErrorHintsBuilder::default()
Expand All @@ -42,6 +55,13 @@ pub fn load(matches: &ArgMatches) -> Config {
);
}

configs
}

/// Load config from file, based on CLI arguments.
///
/// Quits with an error message on failure.
pub fn load_file(path: PathBuf) -> Config {
// Load config
let config = match Config::load(path) {
Ok(config) => config,
Expand Down Expand Up @@ -135,6 +155,8 @@ impl Config {
#[serde(default)]
pub struct Public {
/// Public address.
///
/// The address lazymc will bind to and listen for incoming connections.
#[serde(deserialize_with = "to_socket_addrs")]
pub address: SocketAddr,

Expand Down Expand Up @@ -167,6 +189,12 @@ pub struct Server {
/// Start command.
pub command: String,

/// Server Name.
///
/// Incoming connections will be routed to this server according to the name in the handshake packet.
/// If no name is provided this server will act as a "catch-all" and be routed all connections where no other names match.
pub name: Option<String>,

/// Server address.
#[serde(
deserialize_with = "to_socket_addrs",
Expand Down
4 changes: 1 addition & 3 deletions src/join/forward.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::sync::Arc;

use bytes::BytesMut;
use tokio::net::TcpStream;

Expand All @@ -11,7 +9,7 @@ use super::MethodResult;

/// Forward the client.
pub async fn occupy(
config: Arc<Config>,
config: &Config,
inbound: TcpStream,
inbound_history: &mut BytesMut,
) -> Result<MethodResult, ()> {
Expand Down
12 changes: 5 additions & 7 deletions src/join/hold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ use bytes::BytesMut;
use tokio::net::TcpStream;
use tokio::time;

use crate::config::*;
use crate::server::{Server, State};
use crate::service;

use super::MethodResult;

/// Hold the client.
pub async fn occupy(
config: Arc<Config>,
server: Arc<Server>,
inbound: TcpStream,
inbound_history: &mut BytesMut,
Expand All @@ -27,8 +25,8 @@ pub async fn occupy(
}

// Start holding, consume client
if hold(&config, &server).await? {
service::server::route_proxy_queue(inbound, config, inbound_history.clone());
if hold(&server).await? {
service::server::route_proxy_queue(inbound, &server.config, inbound_history.clone());
return Ok(MethodResult::Consumed);
}

Expand All @@ -39,7 +37,7 @@ pub async fn occupy(
///
/// Returns holding status. `true` if client is held and it should be proxied, `false` it was held
/// but it timed out.
async fn hold<'a>(config: &Config, server: &Server) -> Result<bool, ()> {
async fn hold<'a>(server: &Server) -> Result<bool, ()> {
trace!(target: "lazymc", "Started holding client");

// A task to wait for suitable server state
Expand Down Expand Up @@ -78,7 +76,7 @@ async fn hold<'a>(config: &Config, server: &Server) -> Result<bool, ()> {
};

// Wait for server state with timeout
let timeout = Duration::from_secs(config.join.hold.timeout as u64);
let timeout = Duration::from_secs(server.config.join.hold.timeout as u64);
match time::timeout(timeout, task_wait).await {
// Relay client to proxy
Ok(true) => {
Expand All @@ -94,7 +92,7 @@ async fn hold<'a>(config: &Config, server: &Server) -> Result<bool, ()> {

// Timeout reached, kick with starting message
Err(_) => {
warn!(target: "lazymc", "Held client reached timeout of {}s", config.join.hold.timeout);
warn!(target: "lazymc", "Held client reached timeout of {}s", server.config.join.hold.timeout);
Ok(false)
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/join/kick.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use tokio::net::TcpStream;

use crate::config::*;
use crate::net;
use crate::proto::action;
use crate::proto::client::Client;
Expand All @@ -11,7 +10,6 @@ use super::MethodResult;
/// Kick the client.
pub async fn occupy(
client: &Client,
config: &Config,
server: &Server,
mut inbound: TcpStream,
) -> Result<MethodResult, ()> {
Expand All @@ -20,9 +18,9 @@ pub async fn occupy(
// Select message and kick
let msg = match server.state() {
server::State::Starting | server::State::Stopped | server::State::Started => {
&config.join.kick.starting
&server.config.join.kick.starting
}
server::State::Stopping => &config.join.kick.stopping,
server::State::Stopping => &server.config.join.kick.stopping,
};
action::kick(client, msg, &mut inbound.split().1).await?;

Expand Down
5 changes: 2 additions & 3 deletions src/join/lobby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,20 @@ use super::MethodResult;
pub async fn occupy(
client: &Client,
client_info: ClientInfo,
config: Arc<Config>,
server: Arc<Server>,
inbound: TcpStream,
inbound_queue: BytesMut,
) -> Result<MethodResult, ()> {
trace!(target: "lazymc", "Using lobby method to occupy joining client");

// Must be ready to lobby
if must_still_probe(&config, &server).await {
if must_still_probe(&server.config, &server).await {
warn!(target: "lazymc", "Client connected but lobby is not ready, using next join method, probing not completed");
return Ok(MethodResult::Continue(inbound));
}

// Start lobby
lobby::serve(client, client_info, inbound, config, server, inbound_queue).await?;
lobby::serve(client, client_info, inbound, server, inbound_queue).await?;

// TODO: do not consume client here, allow other join method on fail

Expand Down
18 changes: 4 additions & 14 deletions src/join/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ pub enum MethodResult {
pub async fn occupy(
client: Client,
#[allow(unused_variables)] client_info: ClientInfo,
config: Arc<Config>,
server: Arc<Server>,
mut inbound: TcpStream,
mut inbound_history: BytesMut,
Expand All @@ -43,26 +42,18 @@ pub async fn occupy(
);

// Go through all configured join methods
for method in &config.join.methods {
for method in &server.config.join.methods {
// Invoke method, take result
let result = match method {
// Kick method, immediately kick client
Method::Kick => kick::occupy(&client, &config, &server, inbound).await?,
Method::Kick => kick::occupy(&client, &server, inbound).await?,

// Hold method, hold client connection while server starts
Method::Hold => {
hold::occupy(
config.clone(),
server.clone(),
inbound,
&mut inbound_history,
)
.await?
}
Method::Hold => hold::occupy(server.clone(), inbound, &mut inbound_history).await?,

// Forward method, forward client connection while server starts
Method::Forward => {
forward::occupy(config.clone(), inbound, &mut inbound_history).await?
forward::occupy(&server.config, inbound, &mut inbound_history).await?
}

// Lobby method, keep client in lobby while server starts
Expand All @@ -71,7 +62,6 @@ pub async fn occupy(
lobby::occupy(
&client,
client_info.clone(),
config.clone(),
server.clone(),
inbound,
login_queue.clone(),
Expand Down
18 changes: 8 additions & 10 deletions src/lobby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ pub async fn serve(
client: &Client,
client_info: ClientInfo,
mut inbound: TcpStream,
config: Arc<Config>,
server: Arc<Server>,
queue: BytesMut,
) -> Result<(), ()> {
Expand Down Expand Up @@ -98,7 +97,7 @@ pub async fn serve(
debug!(target: "lazymc::lobby", "Login on lobby server (user: {})", login_start.name);

// Replay Forge payload
if config.server.forge {
if server.config.server.forge {
forge::replay_login_payload(client, &mut inbound, server.clone(), &mut inbound_buf)
.await?;
let (_returned_reader, returned_writer) = inbound.split();
Expand All @@ -122,12 +121,12 @@ pub async fn serve(
send_lobby_play_packets(client, &client_info, &mut writer, &server).await?;

// Wait for server to come online
stage_wait(client, &client_info, &server, &config, &mut writer).await?;
stage_wait(client, &client_info, &server, &mut writer).await?;

// Start new connection to server
let server_client_info = client_info.clone();
let (server_client, mut outbound, mut server_buf) =
connect_to_server(&server_client_info, &inbound, &config).await?;
connect_to_server(&server_client_info, &inbound, &server.config).await?;
let (returned_reader, returned_writer) = inbound.split();
reader = returned_reader;
writer = returned_writer;
Expand All @@ -145,7 +144,7 @@ pub async fn serve(
packets::play::title::send(client, &client_info, &mut writer, "").await?;

// Play ready sound if configured
play_lobby_ready_sound(client, &client_info, &mut writer, &config).await?;
play_lobby_ready_sound(client, &client_info, &mut writer, &server.config).await?;

// Wait a second because Notchian servers are slow
// See: https://wiki.vg/Protocol#Login_Success
Expand Down Expand Up @@ -287,19 +286,18 @@ async fn stage_wait(
client: &Client,
client_info: &ClientInfo,
server: &Server,
config: &Config,
writer: &mut WriteHalf<'_>,
) -> Result<(), ()> {
select! {
a = keep_alive_loop(client, client_info, writer, config) => a,
b = wait_for_server(server, config) => b,
a = keep_alive_loop(client, client_info, writer, &server.config) => a,
b = wait_for_server(server) => b,
}
}

/// Wait for the server to come online.
///
/// Returns `Ok(())` once the server is online, returns `Err(())` if waiting failed.
async fn wait_for_server(server: &Server, config: &Config) -> Result<(), ()> {
async fn wait_for_server(server: &Server) -> Result<(), ()> {
debug!(target: "lazymc::lobby", "Waiting on server to come online...");

// A task to wait for suitable server state
Expand Down Expand Up @@ -331,7 +329,7 @@ async fn wait_for_server(server: &Server, config: &Config) -> Result<(), ()> {
};

// Wait for server state with timeout
let timeout = Duration::from_secs(config.join.lobby.timeout as u64);
let timeout = Duration::from_secs(server.config.join.lobby.timeout as u64);
match time::timeout(timeout, task_wait).await {
// Relay client to proxy
Ok(true) => {
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub(crate) mod os;
pub(crate) mod probe;
pub(crate) mod proto;
pub(crate) mod proxy;
pub(crate) mod router;
pub(crate) mod server;
pub(crate) mod service;
pub(crate) mod status;
Expand Down
Loading