mirror of
https://github.com/timvisee/lazymc.git
synced 2025-07-25 17:21:59 -07:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1d92964802 | ||
|
9b8d569628 | ||
|
94f2fa01e2 | ||
|
9b71052b61 | ||
|
0049ad456c | ||
|
0f2d7720af | ||
|
723ebabcfb | ||
|
f95682fcd5 | ||
|
d5c854d16f | ||
|
493e24ff4d | ||
|
6916800aeb | ||
|
e7c31f2619 | ||
|
7da467ff8c | ||
|
c9d7af0e3c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
|
||||
# Test server
|
||||
/mcserver
|
||||
/bettermc
|
||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# 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
|
||||
|
||||
## 0.2.1 (2021-11-17)
|
||||
|
||||
- Add support for using host names in config address fields
|
||||
|
68
Cargo.lock
generated
68
Cargo.lock
generated
@@ -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"
|
||||
@@ -163,6 +163,12 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -385,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"
|
||||
@@ -747,9 +759,11 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lazymc"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"base64",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -764,6 +778,7 @@ dependencies = [
|
||||
"named-binary-tag",
|
||||
"notify",
|
||||
"pretty_env_logger",
|
||||
"proxy-protocol",
|
||||
"quartz_nbt",
|
||||
"rand 0.8.4",
|
||||
"rcon",
|
||||
@@ -780,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"
|
||||
@@ -815,7 +830,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
[[package]]
|
||||
name = "minecraft-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=a14b40e#a14b40ea9d9a9ed54a6f6546b6d19bc0db1b6c8c"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=356ea54#356ea5424374c5a7249be2f0f13fd3e0e2db5b58"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"minecraft-protocol-derive",
|
||||
@@ -828,7 +843,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "minecraft-protocol-derive"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=a14b40e#a14b40ea9d9a9ed54a6f6546b6d19bc0db1b6c8c"
|
||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=356ea54#356ea5424374c5a7249be2f0f13fd3e0e2db5b58"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1083,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"
|
||||
@@ -1270,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",
|
||||
@@ -1357,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",
|
||||
@@ -1387,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"
|
||||
|
13
Cargo.toml
13
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lazymc"
|
||||
version = "0.2.1"
|
||||
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.
|
||||
@@ -32,6 +32,7 @@ lobby = ["named-binary-tag", "quartz_nbt", "uuid"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
base64 = "0.13"
|
||||
bytes = "1.1"
|
||||
chrono = "0.4"
|
||||
clap = { version = "3.0.0-beta.5", default-features = false, features = [ "std", "cargo", "color", "env", "suggestions", "unicode" ]}
|
||||
@@ -41,20 +42,22 @@ dotenv = "0.15"
|
||||
flate2 = { version = "1.0", default-features = false, features = ["default"] }
|
||||
futures = { version = "0.3", default-features = false, features = ["executor"] }
|
||||
log = "0.4"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "a14b40e" }
|
||||
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"
|
||||
shlex = "1.1"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal", "sync"] }
|
||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal", "sync", "fs"] }
|
||||
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 }
|
||||
|
18
README.md
18
README.md
@@ -43,7 +43,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
|
||||
- _Lobby: keep client in emulated server with lobby world, teleport to real server when ready ([experimental*](./docs/join-method-lobby.md))_
|
||||
- Customizable MOTD and login messages
|
||||
- Automatically manages `server.properties` (host, port and RCON settings)
|
||||
- Automatically handle banned IPs from server within `lazymc`
|
||||
- Automatically block banned IPs from server within `lazymc`
|
||||
- Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix)
|
||||
- Restart server on crash
|
||||
- Lockout mode
|
||||
@@ -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
|
||||
|
4
TODO.md
4
TODO.md
@@ -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
28
docs/extras.md
Normal 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
39
docs/protocol-version.md
Normal 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
79
docs/proxy-ip.md
Normal 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.
|
@@ -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
|
||||
|
@@ -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.0"
|
||||
version = "0.2.3"
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(),
|
||||
);
|
||||
|
19
src/lobby.rs
19
src/lobby.rs
@@ -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),
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
|
||||
|
99
src/proxy.rs
99
src/proxy.rs
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -8,6 +8,7 @@ use minecraft_protocol::encoder::Encoder;
|
||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||
use minecraft_protocol::version::v1_14_4::login::LoginStart;
|
||||
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
@@ -25,6 +26,9 @@ const BAN_MESSAGE_PREFIX: &str = "Your IP address is banned from this server.\nR
|
||||
/// Default ban reason if unknown.
|
||||
const DEFAULT_BAN_REASON: &str = "Banned by an operator.";
|
||||
|
||||
/// Server icon file path.
|
||||
const SERVER_ICON_FILE: &str = "server-icon.png";
|
||||
|
||||
/// Proxy the given inbound stream to a target address.
|
||||
// TODO: do not drop error here, return Box<dyn Error>
|
||||
pub async fn serve(
|
||||
@@ -226,6 +230,13 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
|
||||
}
|
||||
};
|
||||
|
||||
// Get server favicon
|
||||
let favicon = if config.motd.from_server && status.is_some() {
|
||||
status.as_ref().unwrap().favicon.clone()
|
||||
} else {
|
||||
favicon(&config).await
|
||||
};
|
||||
|
||||
// Build status resposne
|
||||
ServerStatus {
|
||||
version,
|
||||
@@ -235,5 +246,36 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
|
||||
max,
|
||||
sample: vec![],
|
||||
},
|
||||
favicon,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get server status favicon.
|
||||
async fn favicon(config: &Config) -> Option<String> {
|
||||
// Get server dir
|
||||
let dir = match config.server.directory.as_ref() {
|
||||
Some(dir) => dir,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// Server icon file, ensure it exists
|
||||
let path = dir.join(SERVER_ICON_FILE);
|
||||
if !path.is_file() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Read icon data
|
||||
let data = fs::read(path)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
// Format and return favicon
|
||||
Some(format!(
|
||||
"{}{}",
|
||||
"data:image/png;base64,",
|
||||
base64::encode(data)
|
||||
))
|
||||
}
|
||||
|
Reference in New Issue
Block a user