12 Commits

Author SHA1 Message Date
timvisee
1d92964802 Bump version to 0.2.3 2021-11-22 18:55:50 +01:00
timvisee
9b8d569628 Update dependencies 2021-11-22 18:53:52 +01:00
timvisee
94f2fa01e2 Update TODO 2021-11-22 18:53:23 +01:00
timvisee
9b71052b61 Add extras document with recommendations and tips after installing 2021-11-22 18:51:06 +01:00
timvisee
0049ad456c Bump rcon to 0.5.2, which now includes changes from our fork 2021-11-22 18:36:12 +01:00
timvisee
0f2d7720af Add documentation for proper IP proxying with PROXY header 2021-11-22 18:35:40 +01:00
timvisee
723ebabcfb Minor monitoring tweaks 2021-11-22 17:57:44 +01:00
timvisee
f95682fcd5 Only enable RCON by default on Windows 2021-11-22 17:57:28 +01:00
timvisee
d5c854d16f Add option to send HAProxy header with RCON connections 2021-11-22 17:55:51 +01:00
timvisee
493e24ff4d Send proxy header with monitor requests 2021-11-22 16:53:35 +01:00
timvisee
6916800aeb Add send_proxy_v2 option to send HAProxy header to server 2021-11-22 14:37:07 +01:00
timvisee
e7c31f2619 Add protocol version documentation 2021-11-22 13:37:21 +01:00
19 changed files with 437 additions and 63 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
# Test server
/mcserver
/bettermc

View File

@@ -1,5 +1,11 @@
# Changelog
## 0.2.3 (2021-11-22)
- Add support for `PROXY` header to notify Minecraft server of real client IP
- Only enable RCON by default on Windows
- Update dependencies
## 0.2.2 (2021-11-18)
- Add server favicon to status response

57
Cargo.lock generated
View File

@@ -19,9 +19,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.45"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
[[package]]
name = "async-channel"
@@ -391,6 +391,12 @@ dependencies = [
"syn",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dotenv"
version = "0.15.0"
@@ -753,9 +759,10 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lazymc"
version = "0.2.2"
version = "0.2.3"
dependencies = [
"anyhow",
"async-std",
"base64",
"bytes",
"chrono",
@@ -771,6 +778,7 @@ dependencies = [
"named-binary-tag",
"notify",
"pretty_env_logger",
"proxy-protocol",
"quartz_nbt",
"rand 0.8.4",
"rcon",
@@ -787,9 +795,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.107"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
[[package]]
name = "linked-hash-map"
@@ -1090,6 +1098,16 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "proxy-protocol"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e50c72c21c738f5c5f350cc33640aee30bf7cd20f9d9da20ed41bce2671d532"
dependencies = [
"bytes",
"snafu",
]
[[package]]
name = "quartz_nbt"
version = "0.2.4"
@@ -1277,9 +1295,9 @@ dependencies = [
[[package]]
name = "rcon"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6f903164a399084787547a026b83e7937bc576d8acdbd9e41ebf5de90a85"
checksum = "6b7fdd146f86bd90fa2d4cf83a28b45f058e90bcf11ed0cce134e757928771e6"
dependencies = [
"async-std",
"bytes",
@@ -1364,9 +1382,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
dependencies = [
"itoa",
"ryu",
@@ -1394,6 +1412,27 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "snafu"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "socket2"
version = "0.4.2"

View File

@@ -1,6 +1,6 @@
[package]
name = "lazymc"
version = "0.2.2"
version = "0.2.3"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
license = "GPL-3.0"
readme = "README.md"
@@ -24,7 +24,7 @@ default = ["rcon", "lobby"]
# RCON support
# Allow use of RCON to manage (stop) server.
# Required on Windows.
rcon = ["rust_rcon"]
rcon = ["rust_rcon", "async-std"]
# Lobby support
# Add lobby join method, keeps client in fake lobby world until server is ready.
@@ -45,6 +45,7 @@ log = "0.4"
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "356ea54" }
notify = "4.0"
pretty_env_logger = "0.4"
proxy-protocol = "0.5"
rand = "0.8"
serde = "1.0"
serde_json = "1.0"
@@ -55,7 +56,8 @@ toml = "0.5"
version-compare = "0.1"
# Feature: rcon
rust_rcon = { package = "rcon", version = "0.5", optional = true }
rust_rcon = { package = "rcon", version = "0.5.2", optional = true }
async-std = { version = "1.9.0", deafult-features = false, optional = true }
# Feature: lobby
named-binary-tag = { version = "0.6", optional = true }

View File

@@ -94,20 +94,14 @@ nano lazymc.toml
lazymc start
```
Before you use this in production, please ensure starting and stopping the
server works as expected by connecting to it once. Watch `lazymc`s output while
it starts and stops. If stopping results in errors, fix this first to prevent
corrupting world/user data.
Please see [extras](./docs/extras.md) for recommendations and additional things
to set up (e.g. how to fix incorrect client IPs and IP banning on your server).
Follow this repository with the _Watch_ button on the top right to be notified of new releases.
Everything should now be ready to go! Connect with your Minecraft client to wake
your server up!
After you've read through the [extras](./docs/extras.md), everything should now
be ready to go! Connect with your Minecraft client to wake your server up!
_Note: If a binary for your system isn't provided, please [compile from
source](#compile-from-source)._
_Note: Installation options are limited at this moment. More will be added
source](#compile-from-source). Installation options are limited at this moment. More will be added
later._
[latest-release]: https://github.com/timvisee/lazymc/releases/latest

View File

@@ -4,15 +4,13 @@
- Resolve TODOs in code
- Don't drop errors, handle everywhere where needed (some were dropped while
prototyping to speed up development)
- Spigot/paper plugin to get real player IP from lazymc, reimplement ban-ip
## Nice to have
- Use server whitelist/blacklist
- Console error if server already started on port, not through `lazymc`
- Kick with message if proxy-to-server connection fails for new client.
- Kick with message if proxy-to-server connection fails for new client.
- Test configuration on start (server dir exists, command not empty)
- Also quit `lazymc` after CTRL+C signal, after server has stopped
- Dynamically increase/decrease server polling interval based on server state
- Server polling through query (`enable-query` in `server.properties`, uses GameSpy4 protocol)

28
docs/extras.md Normal file
View File

@@ -0,0 +1,28 @@
# Extras
Some extra steps and recommendations when using lazymc:
Before you use this in production, always ensure starting and stopping the
server works as expected by connecting to it once. Watch `lazymc`s output while
it starts and stops. If stopping results in errors, fix this first to prevent
corrupting world/user data.
Follow this repository with the _Watch_ button on the top right to be notified
of new releases.
## Recommended
- [Protocol version](./protocol-version.md):
_set correct Minecraft protocol version for the best client compatability_
- [Proxy IP](./proxy-ip.md):
_fix incorrect client IPs on Minecraft server, notify server of correct IP with `PROXY` header_
## Tips
- [bash with start command](./command_bash.md):
_how to properly use a bash script as server start command_
## Experimental features
- [Join method: lobby](./join-method-lobby.md):
_keep clients in fake lobby world while server starts, teleport to real server when ready_

39
docs/protocol-version.md Normal file
View File

@@ -0,0 +1,39 @@
# Protocol version
The Minecraft protocol uses a version number to distinguish between different
protocol versions. Each new Minecraft version having a change in its protocol
gets a new protocol version.
## List of versions
- https://wiki.vg/Protocol_version_numbers#Versions_after_the_Netty_rewrite
## Configuration
In `lazymc` you may configure what protocol version to use:
[`lazymc.toml`](../res/lazymc.toml):
```bash
# -- snip --
[public]
# Server version & protocol hint.
# Sent to clients until actual server version is known.
# See: https://git.io/J1Fvx
version = "1.17.1"
protocol = 756
# -- snip --
```
It is highly recommended to set these to match that of your server version to
allow the best compatibility with clients.
- Set `public.protocol` to the number matching that of your server version
(see [this](#list-of-versions) list)
- Set `public.version` to any string you like. Shows up in read in clients that
have an incompatibel protocol version number
These are used as hint. `lazymc` will automatically use the protocol version of
your Minecraft server once it has started at least once.

79
docs/proxy-ip.md Normal file
View File

@@ -0,0 +1,79 @@
# Proxy IP
lazymc acts as a proxy most of the time. Because of this the Minecraft server
will think all clients connect from the same IP, being the IP lazymc proxies
from.
This breaks IP banning (`/ban-ip`, amongst other IP related things). This may be
a problematic issue for your server.
Luckily, this can be fixed with the [proxy header](#proxy-header). lazymc has
support for this, and can be used with a companion plugin on your server.
## Proxy header
The `PROXY` header may be used to notify the Minecraft server of the real client
IP.
When a new connection is opened to the Minecraft server, the Minecraft server
will read the `PROXY` header with client-IP information. Once read, it will set
the correct client IP internally and will resume communicating with the client
normally.
To enable this with lazymc you must do two things:
- [Modify the lazymc configuration](#configuration)
- [Install a companion plugin](#server-plugin)
## Configuration
To use the `PROXY` header with your Minecraft server, set `server.send_proxy_v2`
to `true`.
[`lazymc.toml`](../res/lazymc.toml):
```toml
# -- snip --
[server]
send_proxy_v2 = true
# -- snip --
```
Other related properties, you probably won't need to touch, include:
- `server.send_proxy_v2`: set to `true` to enable `PROXY` header for Minecraft server
- `join.forward.send_proxy_v2`: set to `true` to enable `PROXY` header forwarded server, if `forward` join method is used
- `rcon.send_proxy_v2`: set to `true` to enable `PROXY` header for RCON connections for Minecraft server
## Server plugin
Install one of these plugins as companion on your server to enable support for
the `PROXY` header. This requires Minecraft server software supporting plugins,
the vanilla Minecraft server does not support this.
If lazymc connects to a Spigot compatible server, use any of:
- https://github.com/riku6460/SpigotProxy ([JAR](https://github.com/riku6460/SpigotProxy/releases/latest))
- https://github.com/timvisee/spigot-proxy
If lazymc connects to a BungeeCord server, use any of:
- https://github.com/MinelinkNetwork/BungeeProxy
## Warning: connection failures
Use of the `PROXY` header must be enabled or disabled on both lazymc and your
Minecraft server using a companion plugin.
If either of the two is missing or misconfigured, it will result in connection
failures due to a missing or unrecognized header.
## Warning: fake IP
When enabling the `PROXY` header on your Minecraft server, malicious parties may
send this header to fake their real IP.
To solve this, make sure the Minecraft server is only publicly reachable through
lazymc. This can be done by setting the Minecraft server IP to a local address
only, or by setting up firewall rules.

View File

@@ -31,15 +31,11 @@ notepad lazymc.toml
.\lazymc start
```
Before you use this in production, please ensure starting and stopping the
server works as expected by connecting to it once. Watch `lazymc`s output while
it starts and stops. If stopping results in errors, fix this first to prevent
corrupting world/user data.
Please see [extras](./extras.md) for recommendations and additional things
to set up (e.g. how to fix incorrect client IPs and IP banning on your server).
Follow this repository with the _Watch_ button on the top right to be notified of new releases.
Everything should now be ready to go! Connect with your Minecraft client to wake
your server up!
After you've read through the [extras](./extras.md), everything should now
be ready to go! Connect with your Minecraft client to wake your server up!
_Note: if you put `lazymc` in `PATH`, or if you
[install](../README.md#compile-from-source) it through Cargo, you can invoke

View File

@@ -8,7 +8,7 @@
# You can probably leave the rest as-is.
#
# You may generate a new configuration with: lazymc config generate
# Or find the latest at: https://is.gd/WWBIQu
# Or find the latest at: https://git.io/J1Fvq
[public]
# Public address. IP and port users connect to.
@@ -17,7 +17,7 @@
# Server version & protocol hint.
# Sent to clients until actual server version is known.
# See: https://is.gd/FTQKTP
# See: https://git.io/J1Fvx
#version = "1.17.1"
#protocol = 756
@@ -30,7 +30,7 @@
directory = "."
# Command to start the server.
# Warning: if using a bash script read: https://is.gd/k8SQYv
# Warning: if using a bash script read: https://git.io/J1FvZ
command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
# Immediately wake server when starting lazymc.
@@ -51,6 +51,10 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
# On connect, clients show a 'Disconnected' message rather than the ban reason.
#drop_banned_ips = false
# Add HAProxy v2 header to proxied connections.
# See: https://git.io/J1bYb
#send_proxy_v2 = false
[time]
# Sleep after number of seconds.
#sleep_after = 60
@@ -105,6 +109,10 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
# The target server will receive original client handshake and login request as received by lazymc.
#address = "127.0.0.1:25565"
# Add HAProxy v2 header to forwarded connections.
# See: https://git.io/J1bYb
#send_proxy_v2 = false
[join.lobby]
# Lobby occupation method.
# The client joins a fake lobby server with an empty world, floating in space.
@@ -141,7 +149,7 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
[rcon]
# Enable sleeping server through RCON.
# Must be enabled on Windows.
#enabled = true
#enabled = false # default: false, true on Windows
# Server RCON port. Must differ from public and server port.
#port = 25575
@@ -151,6 +159,10 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
#password = ""
#randomize_password = true
# Add HAProxy v2 header to RCON connections.
# See: https://git.io/J1bYb
#send_proxy_v2 = false
[advanced]
# Automatically update values in Minecraft server.properties file as required.
#rewrite_server_properties = true
@@ -158,4 +170,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.2"
version = "0.2.3"

View File

@@ -188,6 +188,10 @@ pub struct Server {
/// Drop connections from banned IPs.
#[serde(default)]
pub drop_banned_ips: bool,
/// Add HAProxy v2 header to proxied connections.
#[serde(default)]
pub send_proxy_v2: bool,
}
/// Time configuration.
@@ -333,12 +337,17 @@ pub struct JoinForward {
/// IP and port to forward to.
#[serde(deserialize_with = "to_socket_addrs")]
pub address: SocketAddr,
/// Add HAProxy v2 header to proxied connections.
#[serde(default)]
pub send_proxy_v2: bool,
}
impl Default for JoinForward {
fn default() -> Self {
Self {
address: "127.0.0.1:25565".parse().unwrap(),
send_proxy_v2: false,
}
}
}
@@ -401,15 +410,19 @@ pub struct Rcon {
/// Randomize server RCON password on each start.
pub randomize_password: bool,
/// Add HAProxy v2 header to RCON connections.
pub send_proxy_v2: bool,
}
impl Default for Rcon {
fn default() -> Self {
Self {
enabled: true,
enabled: cfg!(windows),
port: 25575,
password: "".into(),
randomize_password: true,
send_proxy_v2: false,
}
}
}

View File

@@ -4,6 +4,7 @@ use bytes::BytesMut;
use tokio::net::TcpStream;
use crate::config::*;
use crate::proxy::ProxyHeader;
use crate::service;
use super::MethodResult;
@@ -20,6 +21,7 @@ pub async fn occupy(
service::server::route_proxy_address_queue(
inbound,
ProxyHeader::Proxy.not_none(config.join.forward.send_proxy_v2),
config.join.forward.address,
inbound_history.clone(),
);

View File

@@ -15,6 +15,7 @@ use minecraft_protocol::version::v1_17_1::game::{
PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
};
use nbt::CompoundTag;
use tokio::io::AsyncWriteExt;
use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::TcpStream;
use tokio::select;
@@ -128,7 +129,10 @@ pub async fn serve(
// Wait for server to come online, then set up new connection to it
stage_wait(client, &server, &config, &mut writer).await?;
let (server_client, mut outbound, mut server_buf) =
connect_to_server(client_info, &config).await?;
connect_to_server(client_info, &inbound, &config).await?;
let (returned_reader, returned_writer) = inbound.split();
reader = returned_reader;
writer = returned_writer;
// Grab join game packet from server
let join_game =
@@ -562,11 +566,12 @@ async fn wait_for_server(server: &Server, config: &Config) -> Result<(), ()> {
/// This will initialize the connection to the play state. Client details are used.
async fn connect_to_server(
client_info: ClientInfo,
inbound: &TcpStream,
config: &Config,
) -> Result<(Client, TcpStream, BytesMut), ()> {
time::timeout(
SERVER_CONNECT_TIMEOUT,
connect_to_server_no_timeout(client_info, config),
connect_to_server_no_timeout(client_info, inbound, config),
)
.await
.map_err(|_| {
@@ -580,6 +585,7 @@ async fn connect_to_server(
// TODO: clean this up
async fn connect_to_server_no_timeout(
client_info: ClientInfo,
inbound: &TcpStream,
config: &Config,
) -> Result<(Client, TcpStream, BytesMut), ()> {
// Open connection
@@ -588,6 +594,15 @@ async fn connect_to_server_no_timeout(
.await
.map_err(|_| ())?;
// Add proxy header
if config.server.send_proxy_v2 {
trace!(target: "lazymc::lobby", "Sending client proxy header for server connection");
outbound
.write_all(&proxy::stream_proxy_header(inbound).map_err(|_| ())?)
.await
.map_err(|_| ())?;
}
// Construct temporary server client
let tmp_client = match outbound.local_addr() {
Ok(addr) => Client::new(addr),

View File

@@ -1,8 +1,13 @@
use std::time::Duration;
use async_std::net::TcpStream;
use async_std::prelude::*;
use rust_rcon::{Connection, Error as RconError};
use tokio::time;
use crate::config::Config;
use crate::proxy;
/// Minecraft RCON quirk.
///
/// Wait this time between RCON operations.
@@ -17,16 +22,39 @@ pub struct Rcon {
impl Rcon {
/// Connect to a host.
pub async fn connect(addr: &str, pass: &str) -> Result<Self, Box<dyn std::error::Error>> {
pub async fn connect(
config: &Config,
addr: &str,
pass: &str,
) -> Result<Self, Box<dyn std::error::Error>> {
// Connect to our TCP stream
let mut stream = TcpStream::connect(addr).await?;
// Add proxy header
if config.rcon.send_proxy_v2 {
trace!(target: "lazymc::rcon", "Sending local proxy header for RCON connection");
stream.write_all(&proxy::local_proxy_header()?).await?;
}
// Start connection
let con = Connection::builder()
.enable_minecraft_quirks(true)
.connect(addr, pass)
.connect_stream(stream, pass)
.await?;
Ok(Self { con })
}
/// Connect to a host from the given configuration.
pub async fn connect_config(config: &Config) -> Result<Self, Box<dyn std::error::Error>> {
// RCON address
let mut addr = config.server.address;
addr.set_port(config.rcon.port);
let addr = addr.to_string();
Self::connect(config, &addr, &config.rcon.password).await
}
/// Send command over RCON.
pub async fn cmd(&mut self, cmd: &str) -> Result<String, RconError> {
// Minecraft quirk

View File

@@ -1,5 +1,3 @@
// TODO: remove all unwraps/expects here!
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
@@ -12,12 +10,14 @@ use minecraft_protocol::version::v1_14_4::status::{
PingRequest, PingResponse, StatusRequest, StatusResponse,
};
use rand::Rng;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::time;
use crate::config::Config;
use crate::proto::client::{Client, ClientState};
use crate::proto::{packet, packets};
use crate::proxy;
use crate::server::{Server, State};
/// Monitor ping inverval in seconds.
@@ -27,7 +27,7 @@ const MONITOR_POLL_INTERVAL: Duration = Duration::from_secs(2);
const STATUS_TIMEOUT: u64 = 20;
/// Ping request timeout in seconds.
const PING_TIMEOUT: u64 = 20;
const PING_TIMEOUT: u64 = 10;
/// Monitor server.
pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
@@ -87,6 +87,7 @@ pub async fn poll_server(
// Try ping fallback if server is currently started
if server.state() == State::Started {
debug!(target: "lazymc::monitor", "Failed to get status from started server, trying ping...");
do_ping(config, addr).await?;
}
@@ -97,6 +98,15 @@ pub async fn poll_server(
async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus, ()> {
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
// Add proxy header
if config.server.send_proxy_v2 {
trace!(target: "lazymc::monitor", "Sending local proxy header for server connection");
stream
.write_all(&proxy::local_proxy_header().map_err(|_| ())?)
.await
.map_err(|_| ())?;
}
// Dummy client
let client = Client::dummy();
@@ -109,6 +119,15 @@ async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus,
async fn do_ping(config: &Config, addr: SocketAddr) -> Result<(), ()> {
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
// Add proxy header
if config.server.send_proxy_v2 {
trace!(target: "lazymc::monitor", "Sending local proxy header for server connection");
stream
.write_all(&proxy::local_proxy_header().map_err(|_| ())?)
.await
.map_err(|_| ())?;
}
// Dummy client
let client = Client::dummy();

View File

@@ -1,6 +1,9 @@
use std::error::Error;
use std::net::SocketAddr;
use bytes::BytesMut;
use proxy_protocol::version2::{ProxyAddresses, ProxyCommand, ProxyTransportProtocol};
use proxy_protocol::EncodeError;
use tokio::io;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
@@ -8,8 +11,12 @@ use tokio::net::TcpStream;
use crate::net;
/// Proxy the inbound stream to a target address.
pub async fn proxy(inbound: TcpStream, addr_target: SocketAddr) -> Result<(), Box<dyn Error>> {
proxy_with_queue(inbound, addr_target, &[]).await
pub async fn proxy(
inbound: TcpStream,
proxy_header: ProxyHeader,
addr_target: SocketAddr,
) -> Result<(), Box<dyn Error>> {
proxy_with_queue(inbound, proxy_header, addr_target, &[]).await
}
/// Proxy the inbound stream to a target address.
@@ -17,12 +24,26 @@ pub async fn proxy(inbound: TcpStream, addr_target: SocketAddr) -> Result<(), Bo
/// Send the queue to the target server before proxying.
pub async fn proxy_with_queue(
inbound: TcpStream,
proxy_header: ProxyHeader,
addr_target: SocketAddr,
queue: &[u8],
) -> Result<(), Box<dyn Error>> {
// Set up connection to server
// TODO: on connect fail, ping server and redirect to serve_status if offline
let outbound = TcpStream::connect(addr_target).await?;
let mut outbound = TcpStream::connect(addr_target).await?;
// Add proxy header
match proxy_header {
ProxyHeader::None => {}
ProxyHeader::Local => {
let header = local_proxy_header()?;
outbound.write_all(&header).await?;
}
ProxyHeader::Proxy => {
let header = stream_proxy_header(&inbound)?;
outbound.write_all(&header).await?;
}
}
// Start proxy on both streams
proxy_inbound_outbound_with_queue(inbound, outbound, &[], queue).await
@@ -71,3 +92,75 @@ pub async fn proxy_inbound_outbound_with_queue(
Ok(())
}
/// Proxy header.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ProxyHeader {
/// Do not add proxy header.
None,
/// Header for locally initiated connection.
Local,
/// Header for proxied connection.
Proxy,
}
impl ProxyHeader {
/// Changes to `None` if `false` if given.
///
/// `None` stays `None`.
pub fn not_none(self, not_none: bool) -> Self {
if not_none {
self
} else {
Self::None
}
}
}
/// Get the proxy header for a locally initiated connection.
///
/// This header may be sent over the outbound stream to signal client information.
pub fn local_proxy_header() -> Result<BytesMut, EncodeError> {
// Build proxy header
let header = proxy_protocol::ProxyHeader::Version2 {
command: ProxyCommand::Local,
transport_protocol: ProxyTransportProtocol::Stream,
addresses: ProxyAddresses::Unspec,
};
proxy_protocol::encode(header)
}
/// Get the proxy header for the given inbound stream.
///
/// This header may be sent over the outbound stream to signal client information.
pub fn stream_proxy_header(inbound: &TcpStream) -> Result<BytesMut, EncodeError> {
// Get peer and local address
let peer = inbound
.peer_addr()
.expect("Peer address not known for TCP stream");
let local = inbound
.local_addr()
.expect("Local address not known for TCP stream");
// Build proxy header
let header = proxy_protocol::ProxyHeader::Version2 {
command: ProxyCommand::Proxy,
transport_protocol: ProxyTransportProtocol::Stream,
addresses: match (peer, local) {
(SocketAddr::V4(source), SocketAddr::V4(destination)) => ProxyAddresses::Ipv4 {
source,
destination,
},
(SocketAddr::V6(source), SocketAddr::V6(destination)) => ProxyAddresses::Ipv6 {
source,
destination,
},
(_, _) => unreachable!(),
},
};
proxy_protocol::encode(header)
}

View File

@@ -498,13 +498,8 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
return false;
}
// RCON address
let mut addr = config.server.address;
addr.set_port(config.rcon.port);
let addr = addr.to_string();
// Create RCON client
let mut rcon = match Rcon::connect(&addr, &config.rcon.password).await {
let mut rcon = match Rcon::connect_config(&config).await {
Ok(rcon) => rcon,
Err(err) => {
error!(target: "lazymc", "Failed to RCON server to sleep: {}", err);
@@ -522,11 +517,11 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
server.rcon_last_stop.lock().await.replace(Instant::now());
server.update_state(State::Stopping, config).await;
drop(rcon_lock);
// Gracefully close connection
rcon.close().await;
drop(rcon_lock);
true
}

View File

@@ -7,7 +7,7 @@ use tokio::net::{TcpListener, TcpStream};
use crate::config::Config;
use crate::proto::client::Client;
use crate::proxy;
use crate::proxy::{self, ProxyHeader};
use crate::server::{self, Server};
use crate::service;
use crate::status;
@@ -114,7 +114,12 @@ fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>, pe
#[inline]
fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
// When server is online, proxy all
let service = proxy::proxy(inbound, config.server.address).map(|r| {
let service = proxy::proxy(
inbound,
ProxyHeader::Proxy.not_none(config.server.send_proxy_v2),
config.server.address,
)
.map(|r| {
if let Err(err) = r {
warn!(target: "lazymc", "Failed to proxy: {}", err);
}
@@ -126,15 +131,25 @@ fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
/// Route inbound TCP stream to proxy with queued data, spawning a new task.
#[inline]
pub fn route_proxy_queue(inbound: TcpStream, config: Arc<Config>, queue: BytesMut) {
route_proxy_address_queue(inbound, config.server.address, queue);
route_proxy_address_queue(
inbound,
ProxyHeader::Proxy.not_none(config.server.send_proxy_v2),
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) {
pub fn route_proxy_address_queue(
inbound: TcpStream,
proxy_header: ProxyHeader,
addr: SocketAddr,
queue: BytesMut,
) {
// When server is online, proxy all
let service = async move {
proxy::proxy_with_queue(inbound, addr, &queue)
proxy::proxy_with_queue(inbound, proxy_header, addr, &queue)
.map(|r| {
if let Err(err) = r {
warn!(target: "lazymc", "Failed to proxy: {}", err);