Skip to content
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