27 Commits

Author SHA1 Message Date
timvisee
0ba46caf5c Bump version to 0.2.4 2021-11-24 14:05:27 +01:00
timvisee
6b23490919 Resolve clippy warnings 2021-11-24 14:02:08 +01:00
timvisee
9e08ed6cda Update dependencies 2021-11-24 13:57:19 +01:00
timvisee
3271db1cb3 Resolve compile warning 2021-11-24 13:55:23 +01:00
timvisee
cf0e3ef15b Make server directory relative to configuration file directory 2021-11-24 13:53:38 +01:00
timvisee
ee21eb45fd Bump minimum supported Minecraft version to 1.7.2 2021-11-24 13:28:06 +01:00
timvisee
aebb5563e0 Only send status response favicon to client versions that support it 2021-11-24 13:27:42 +01:00
timvisee
20fb6ee715 Always include favicon in status response, fall back to default icon
See https://github.com/timvisee/lazymc/issues/11#issuecomment-977814539
2021-11-24 13:20:49 +01:00
timvisee
ea2dbc905c Add Minecraft default server icon, extracted from Minecraft 1.17.1 2021-11-24 13:02:41 +01:00
timvisee
eb66265670 Handle SIGTERM exit code as successful 2021-11-23 13:00:10 +01:00
timvisee
df101ce53b Fix typo in Cargo.toml key 2021-11-22 20:38:30 +01:00
timvisee
8f2ce9b4b8 Fix compilation error without lobby feature 2021-11-22 20:36:46 +01:00
timvisee
20902e6a94 Derive correct UUID for offline players in lobby logic (2/2) 2021-11-22 20:20:08 +01:00
timvisee
3e933f7566 Derive correct UUID for offline players in lobby logic 2021-11-22 20:14:29 +01:00
timvisee
46fa594065 Update features in README 2021-11-22 19:00:36 +01:00
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
28 changed files with 665 additions and 134 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,5 +1,20 @@
# Changelog
## 0.2.4 (2021-11-24)
- Fix status response issues with missing server icon, fall back to default icon
- Fix incorrect UUID for players in lobby logic
- Make server directory relative to configuration file path
- Assume SIGTERM exit code for server process to be successful on Unix
- Update features in README
- Update dependencies
## 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

146
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"
@@ -175,6 +175,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "1.1.0"
@@ -298,9 +307,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
dependencies = [
"cfg-if 1.0.0",
]
@@ -391,6 +400,21 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[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"
@@ -512,9 +536,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
dependencies = [
"futures-channel",
"futures-core",
@@ -527,9 +551,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
dependencies = [
"futures-core",
"futures-sink",
@@ -537,15 +561,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
[[package]]
name = "futures-executor"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
dependencies = [
"futures-core",
"futures-task",
@@ -554,9 +578,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
[[package]]
name = "futures-lite"
@@ -575,23 +599,22 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
[[package]]
name = "futures-task"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
[[package]]
name = "futures-util"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
dependencies = [
"autocfg 1.0.1",
"futures-channel",
"futures-core",
"futures-io",
@@ -603,6 +626,16 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.3"
@@ -753,9 +786,10 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lazymc"
version = "0.2.2"
version = "0.2.4"
dependencies = [
"anyhow",
"async-std",
"base64",
"bytes",
"chrono",
@@ -767,10 +801,12 @@ dependencies = [
"futures",
"libc",
"log",
"md-5",
"minecraft-protocol",
"named-binary-tag",
"notify",
"pretty_env_logger",
"proxy-protocol",
"quartz_nbt",
"rand 0.8.4",
"rcon",
@@ -787,9 +823,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"
@@ -807,6 +843,17 @@ dependencies = [
"value-bag",
]
[[package]]
name = "md-5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]]
name = "md5"
version = "0.6.1"
@@ -1001,6 +1048,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "4.2.0"
@@ -1090,6 +1143,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 +1340,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 +1427,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 +1457,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"
@@ -1520,6 +1604,12 @@ dependencies = [
"serde",
]
[[package]]
name = "typenum"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "unicase"
version = "2.6.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "lazymc"
version = "0.2.2"
version = "0.2.4"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
license = "GPL-3.0"
readme = "README.md"
@@ -24,11 +24,11 @@ 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.
lobby = ["named-binary-tag", "quartz_nbt", "uuid"]
lobby = ["md-5", "named-binary-tag", "quartz_nbt", "uuid"]
[dependencies]
anyhow = "1.0"
@@ -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,9 +56,11 @@ 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", default-features = false, optional = true }
# Feature: lobby
md-5 = { version = "0.9", optional = true }
named-binary-tag = { version = "0.6", optional = true }
quartz_nbt = { version = "0.2", optional = true }
uuid = { version = "0.7", optional = true, features = ["v3"] }

View File

@@ -35,7 +35,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
## Features
- Very efficient, lightweight & low-profile (~3KB RAM)
- Supports Minecraft Java Edition 1.6+, supports modded (e.g. Forge, FTB)
- Supports Minecraft Java Edition 1.7.2+, supports modded (e.g. Forge, FTB)
- Configure joining client occupation methods:
- Hold: hold clients when server starts, relay when ready, without them noticing
- Kick: kick clients when server starts, with a starting message
@@ -44,7 +44,8 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
- Customizable MOTD and login messages
- Automatically manages `server.properties` (host, port and RCON settings)
- Automatically block banned IPs from server within `lazymc`
- Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix)
- Graceful server sleep/shutdown through RCON or `SIGTERM`
- Real client IP on Minecraft server with `PROXY` header ([usage](./docs/proxy-ip.md))
- Restart server on crash
- Lockout mode
@@ -94,20 +95,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.4"

BIN
res/unknown_server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use clap::ArgMatches;
use crate::config::{self, Config};
use crate::config::{self, Config, Server as ConfigServer};
use crate::mc::server_properties;
use crate::proto;
use crate::service;
@@ -120,7 +120,7 @@ fn rewrite_server_properties(config: &Config) {
}
// Ensure server directory is set, it must exist
let dir = match &config.server.directory {
let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir,
None => {
warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE);

View File

@@ -1,7 +1,7 @@
use std::fs;
use std::io;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use clap::ArgMatches;
use serde::Deserialize;
@@ -63,6 +63,12 @@ pub fn load(matches: &ArgMatches) -> Config {
/// Configuration.
#[derive(Debug, Deserialize)]
pub struct Config {
/// Configuration path if known.
///
/// Should be used as base directory for filesystem operations.
#[serde(skip)]
pub path: Option<PathBuf>,
/// Public configuration.
#[serde(default)]
pub public: Public,
@@ -101,9 +107,9 @@ pub struct Config {
impl Config {
/// Load configuration from file.
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let data = fs::read(path)?;
let config: Config = toml::from_slice(&data)?;
pub fn load(path: PathBuf) -> Result<Self, io::Error> {
let data = fs::read(&path)?;
let mut config: Config = toml::from_slice(&data)?;
// Show warning if config version is problematic
match &config.config.version {
@@ -118,6 +124,7 @@ impl Config {
Ok(true) => {}
},
}
config.path.replace(path);
Ok(config)
}
@@ -152,8 +159,10 @@ impl Default for Public {
#[derive(Debug, Deserialize)]
pub struct Server {
/// Server directory.
///
/// Private because you should use `Server::server_directory()` instead.
#[serde(default = "option_pathbuf_dot")]
pub directory: Option<PathBuf>,
directory: Option<PathBuf>,
/// Start command.
pub command: String,
@@ -188,6 +197,23 @@ 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,
}
impl Server {
/// Get the server directory.
///
/// This does not check whether it exists.
pub fn server_directory(config: &Config) -> Option<PathBuf> {
// Get directory, relative to config directory if known
match config.path.as_ref().and_then(|p| p.parent()) {
Some(config_dir) => Some(config_dir.join(config.server.directory.as_ref()?)),
None => config.server.directory.clone(),
}
}
}
/// Time configuration.
@@ -333,12 +359,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 +432,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,14 +15,14 @@ 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;
use tokio::time;
use uuid::Uuid;
use crate::config::*;
use crate::mc;
use crate::mc::{self, uuid};
use crate::net;
use crate::proto;
use crate::proto::client::{Client, ClientInfo, ClientState};
@@ -128,7 +128,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 =
@@ -194,10 +197,7 @@ async fn respond_login_success(
) -> Result<(), ()> {
packet::write_packet(
LoginSuccess {
uuid: Uuid::new_v3(
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
login_start.name.as_bytes(),
),
uuid: uuid::offline_player_uuid(&login_start.name),
username: login_start.name.clone(),
},
client,
@@ -562,11 +562,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 +581,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 +590,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),

26
src/mc/favicon.rs Normal file
View File

@@ -0,0 +1,26 @@
use crate::proto::client::ClientInfo;
/// Protocol version since when favicons are supported.
const FAVICON_PROTOCOL_VERSION: i32 = 4;
/// Get default server status favicon.
pub fn default_favicon() -> String {
encode_favicon(include_bytes!("../../res/unknown_server_optimized.png"))
}
/// Encode favicon bytes to a string Minecraft can read.
///
/// This assumes the favicon data to be a valid PNG image.
pub fn encode_favicon(data: &[u8]) -> String {
format!("{}{}", "data:image/png;base64,", base64::encode(data))
}
/// Check whether the status response favicon is supported based on the given client info.
///
/// Defaults to `true` if unsure.
pub fn supports_favicon(client_info: &ClientInfo) -> bool {
client_info
.protocol_version
.map(|p| p >= FAVICON_PROTOCOL_VERSION)
.unwrap_or(true)
}

View File

@@ -1,7 +1,10 @@
pub mod ban;
pub mod favicon;
#[cfg(feature = "rcon")]
pub mod rcon;
pub mod server_properties;
#[cfg(feature = "lobby")]
pub mod uuid;
/// Minecraft ticks per second.
#[allow(unused)]

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

@@ -11,39 +11,39 @@ const EOL: &str = "\r\n";
/// Try to rewrite changes in server.properties file in dir.
///
/// Prints an error and stops on failure.
pub fn rewrite_dir(dir: &Path, changes: HashMap<&str, String>) {
pub fn rewrite_dir<P: AsRef<Path>>(dir: P, changes: HashMap<&str, String>) {
if changes.is_empty() {
return;
}
// Ensure directory exists
if !dir.is_dir() {
if !dir.as_ref().is_dir() {
warn!(target: "lazymc",
"Not rewriting {} file, configured server directory doesn't exist: {}",
FILE,
dir.to_str().unwrap_or("?")
dir.as_ref().to_str().unwrap_or("?")
);
return;
}
// Rewrite file
rewrite_file(&dir.join(FILE), changes)
rewrite_file(dir.as_ref().join(FILE), changes)
}
/// Try to rewrite changes in server.properties file.
///
/// Prints an error and stops on failure.
pub fn rewrite_file(file: &Path, changes: HashMap<&str, String>) {
pub fn rewrite_file<P: AsRef<Path>>(file: P, changes: HashMap<&str, String>) {
if changes.is_empty() {
return;
}
// File must exist
if !file.is_file() {
if !file.as_ref().is_file() {
warn!(target: "lazymc",
"Not writing {} file, not found at: {}",
FILE,
file.to_str().unwrap_or("?"),
file.as_ref().to_str().unwrap_or("?"),
);
return;
}

33
src/mc/uuid.rs Normal file
View File

@@ -0,0 +1,33 @@
use md5::{Digest, Md5};
use uuid::Uuid;
/// Offline player namespace.
const OFFLINE_PLAYER_NAMESPACE: &str = "OfflinePlayer:";
/// Get UUID for given player username.
pub fn player_uuid(username: &str) -> Uuid {
java_name_uuid_from_bytes(username.as_bytes())
}
/// Get UUID for given offline player username.
pub fn offline_player_uuid(username: &str) -> Uuid {
player_uuid(&format!("{}{}", OFFLINE_PLAYER_NAMESPACE, username))
}
/// Java's `UUID.nameUUIDFromBytes`
///
/// Static factory to retrieve a type 3 (name based) `Uuid` based on the specified byte array.
///
/// Ported from: https://git.io/J1b6A
fn java_name_uuid_from_bytes(data: &[u8]) -> Uuid {
let mut hasher = Md5::new();
hasher.update(data);
let mut md5: [u8; 16] = hasher.finalize().into();
md5[6] &= 0x0f; // clear version
md5[6] |= 0x30; // set to version 3
md5[8] &= 0x3f; // clear variant
md5[8] |= 0x80; // set to IETF variant
Uuid::from_bytes(md5)
}

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,76 @@ 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.
#[allow(unused)]
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

@@ -12,7 +12,7 @@ use tokio::sync::Semaphore;
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
use tokio::time;
use crate::config::Config;
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban::{BannedIp, BannedIps};
use crate::os;
@@ -27,6 +27,10 @@ const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
#[cfg(feature = "rcon")]
const RCON_COOLDOWN: Duration = Duration::from_secs(15);
/// Exit code when SIGTERM is received on Unix.
#[cfg(unix)]
const UNIX_EXIT_SIGTERM: i32 = 130;
/// Shared server state.
#[derive(Debug)]
pub struct Server {
@@ -417,7 +421,7 @@ pub async fn invoke_server_cmd(
cmd.kill_on_drop(true);
// Set working directory
if let Some(ref dir) = config.server.directory {
if let Some(ref dir) = ConfigServer::server_directory(&config) {
cmd.current_dir(dir);
}
@@ -443,6 +447,11 @@ pub async fn invoke_server_cmd(
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
false
}
#[cfg(unix)]
Ok(status) if status.code() == Some(UNIX_EXIT_SIGTERM) => {
debug!(target: "lazymc", "Server process stopped successfully by SIGTERM ({})", status);
false
}
Ok(status) => {
warn!(target: "lazymc", "Server process stopped with error code ({})", status);
state.state() == State::Started
@@ -498,13 +507,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 +526,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

@@ -6,7 +6,7 @@ use std::time::Duration;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use crate::config::Config;
use crate::config::{Config, Server as ConfigServer};
use crate::mc::ban;
use crate::server::Server;
@@ -23,7 +23,7 @@ pub fn service(config: Arc<Config>, server: Arc<Server>) {
}
// Ensure server directory is set, it must exist
let dir = match &config.server.directory {
let dir = match ConfigServer::server_directory(&config) {
Some(dir) => dir,
None => {
warn!(target: "lazymc", "Not blocking banned IPs, server directory not configured, unable to find {} file", ban::FILE);

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);

View File

@@ -12,8 +12,9 @@ use tokio::fs;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use crate::config::*;
use crate::config::{Config, Server as ConfigServer};
use crate::join;
use crate::mc::favicon;
use crate::proto::action;
use crate::proto::client::{Client, ClientInfo, ClientState};
use crate::proto::packet::{self, RawPacket};
@@ -98,7 +99,7 @@ pub async fn serve(
// Hijack server status packet
if client_state == ClientState::Status && packet.id == packets::status::SERVER_STATUS {
let server_status = server_status(&config, &server).await;
let server_status = server_status(&client_info, &config, &server).await;
let packet = StatusResponse { server_status };
let mut data = Vec::new();
@@ -196,7 +197,7 @@ pub async fn serve(
}
/// Build server status object to respond to client with.
async fn server_status(config: &Config, server: &Server) -> ServerStatus {
async fn server_status(client_info: &ClientInfo, config: &Config, server: &Server) -> ServerStatus {
let status = server.status().await;
let server_state = server.state();
@@ -230,12 +231,16 @@ 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
};
// Extract favicon from real server status, load from disk, or use default
let mut favicon = None;
if favicon::supports_favicon(client_info) {
if config.motd.from_server && status.is_some() {
favicon = status.as_ref().unwrap().favicon.clone()
}
if favicon.is_none() {
favicon = Some(server_favicon(config).await);
}
}
// Build status resposne
ServerStatus {
@@ -251,31 +256,31 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
}
/// Get server status favicon.
async fn favicon(config: &Config) -> Option<String> {
///
/// This always returns a favicon, returning the default one if none is set.
async fn server_favicon(config: &Config) -> String {
// Get server dir
let dir = match config.server.directory.as_ref() {
let dir = match ConfigServer::server_directory(config) {
Some(dir) => dir,
None => return None,
None => return favicon::default_favicon(),
};
// Server icon file, ensure it exists
let path = dir.join(SERVER_ICON_FILE);
if !path.is_file() {
return None;
return favicon::default_favicon();
}
// 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()?;
let data = match fs::read(path).await.map_err(|err| {
error!(target: "lazymc", "Failed to read favicon from {}: {}", SERVER_ICON_FILE, err);
}) {
Ok(data) => data,
Err(err) => {
error!(target: "lazymc::status", "Failed to load server icon from disk, using default: {:?}", err);
return favicon::default_favicon();
}
};
// Format and return favicon
Some(format!(
"{}{}",
"data:image/png;base64,",
base64::encode(data)
))
favicon::encode_favicon(&data)
}