Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0ba46caf5c | ||
|
6b23490919 | ||
|
9e08ed6cda | ||
|
3271db1cb3 | ||
|
cf0e3ef15b | ||
|
ee21eb45fd | ||
|
aebb5563e0 | ||
|
20fb6ee715 | ||
|
ea2dbc905c | ||
|
eb66265670 | ||
|
df101ce53b | ||
|
8f2ce9b4b8 | ||
|
20902e6a94 | ||
|
3e933f7566 | ||
|
46fa594065 | ||
|
1d92964802 | ||
|
9b8d569628 | ||
|
94f2fa01e2 | ||
|
9b71052b61 | ||
|
0049ad456c | ||
|
0f2d7720af | ||
|
723ebabcfb | ||
|
f95682fcd5 | ||
|
d5c854d16f | ||
|
493e24ff4d | ||
|
6916800aeb | ||
|
e7c31f2619 | ||
|
7da467ff8c | ||
|
c9d7af0e3c | ||
|
0715baed8c | ||
|
1f4ec11ad1 | ||
|
acf6768b49 | ||
|
75f7b62b16 | ||
|
9cc1958bbd | ||
|
785bd2f33e | ||
|
b168dcefde | ||
|
74d772ab42 | ||
|
a71b3cb24f | ||
|
28dbcdbfd6 | ||
|
e816d4ff6c | ||
|
168cbceb4c | ||
|
b1bd9e1837 | ||
|
ec24f088b2 | ||
|
6321999489 | ||
|
47fe7d0387 | ||
|
7df3829e00 | ||
|
b06f26b3e8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
|
|
||||||
# Test server
|
# Test server
|
||||||
/mcserver
|
/mcserver
|
||||||
|
/bettermc
|
||||||
|
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# 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
|
||||||
|
|
||||||
|
## 0.2.1 (2021-11-17)
|
||||||
|
|
||||||
|
- Add support for using host names in config address fields
|
||||||
|
- Handle banned players within `lazymc` based on server `banned-ips.json`
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
## 0.2.0 (2021-11-15)
|
## 0.2.0 (2021-11-15)
|
||||||
|
|
||||||
- Add lockout feature, enable to kick all connecting clients with a message
|
- Add lockout feature, enable to kick all connecting clients with a message
|
||||||
|
497
Cargo.lock
generated
497
Cargo.lock
generated
@@ -19,9 +19,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.45"
|
version = "1.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
@@ -80,7 +80,7 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -148,7 +148,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -163,12 +163,27 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
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]]
|
[[package]]
|
||||||
name = "blocking"
|
name = "blocking"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -219,12 +234,31 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"time",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.0.0-beta.5"
|
version = "3.0.0-beta.5"
|
||||||
@@ -259,7 +293,7 @@ checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -273,11 +307,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -286,7 +320,7 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -366,6 +400,21 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "dotenv"
|
name = "dotenv"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@@ -414,13 +463,25 @@ dependencies = [
|
|||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -432,6 +493,25 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fsevent-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-cprng"
|
name = "fuchsia-cprng"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -439,13 +519,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "fuchsia-zircon"
|
||||||
version = "0.3.17"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
|
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fuchsia-zircon-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-zircon-sys"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -454,9 +551,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -464,15 +561,26 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
|
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
|
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
@@ -491,28 +599,41 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
|
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 1.0.1",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"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]]
|
[[package]]
|
||||||
@@ -521,7 +642,7 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
@@ -579,13 +700,42 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iovec"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -603,6 +753,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kernel32-sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"winapi-build",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kv-log-macro"
|
name = "kv-log-macro"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -618,12 +778,21 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazymc"
|
name = "lazymc"
|
||||||
version = "0.2.0"
|
version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-std",
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
@@ -632,27 +801,31 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
"md-5",
|
||||||
"minecraft-protocol",
|
"minecraft-protocol",
|
||||||
"named-binary-tag",
|
"named-binary-tag",
|
||||||
|
"notify",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"proxy-protocol",
|
||||||
"quartz_nbt",
|
"quartz_nbt",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"rcon",
|
"rcon",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"shlex",
|
"shlex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"uuid",
|
"uuid",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.107"
|
version = "0.2.108"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
@@ -666,10 +839,21 @@ version = "0.4.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"value-bag",
|
"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]]
|
[[package]]
|
||||||
name = "md5"
|
name = "md5"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -685,7 +869,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "minecraft-protocol"
|
name = "minecraft-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=356ea54#356ea5424374c5a7249be2f0f13fd3e0e2db5b58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"minecraft-protocol-derive",
|
"minecraft-protocol-derive",
|
||||||
@@ -698,7 +882,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "minecraft-protocol-derive"
|
name = "minecraft-protocol-derive"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=d26a525#d26a525c7b29b61d2db64805181fb5471ea4317a"
|
source = "git+https://github.com/timvisee/rust-minecraft-protocol?rev=356ea54#356ea5424374c5a7249be2f0f13fd3e0e2db5b58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -715,6 +899,25 @@ dependencies = [
|
|||||||
"autocfg 1.0.1",
|
"autocfg 1.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.6.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"fuchsia-zircon",
|
||||||
|
"fuchsia-zircon-sys",
|
||||||
|
"iovec",
|
||||||
|
"kernel32-sys",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow 0.2.2",
|
||||||
|
"net2",
|
||||||
|
"slab",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.7.14"
|
version = "0.7.14"
|
||||||
@@ -723,9 +926,33 @@ checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"miow",
|
"miow 0.3.7",
|
||||||
"ntapi",
|
"ntapi",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio-extras"
|
||||||
|
version = "2.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
||||||
|
dependencies = [
|
||||||
|
"lazycell",
|
||||||
|
"log",
|
||||||
|
"mio 0.6.23",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys",
|
||||||
|
"net2",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"ws2_32-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -734,7 +961,7 @@ version = "0.3.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -748,13 +975,61 @@ dependencies = [
|
|||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "net2"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "4.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"filetime",
|
||||||
|
"fsevent",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"libc",
|
||||||
|
"mio 0.6.23",
|
||||||
|
"mio-extras",
|
||||||
|
"walkdir",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.1",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -773,6 +1048,12 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "4.2.0"
|
version = "4.2.0"
|
||||||
@@ -806,11 +1087,11 @@ version = "2.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wepoll-ffi",
|
"wepoll-ffi",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -862,6 +1143,16 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"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]]
|
[[package]]
|
||||||
name = "quartz_nbt"
|
name = "quartz_nbt"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -917,7 +1208,7 @@ dependencies = [
|
|||||||
"rand_os",
|
"rand_os",
|
||||||
"rand_pcg",
|
"rand_pcg",
|
||||||
"rand_xorshift",
|
"rand_xorshift",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1011,7 +1302,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_core 0.4.2",
|
"rand_core 0.4.2",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1025,7 +1316,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"rand_core 0.4.2",
|
"rand_core 0.4.2",
|
||||||
"rdrand",
|
"rdrand",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1049,9 +1340,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rcon"
|
name = "rcon"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "465a6f903164a399084787547a026b83e7937bc576d8acdbd9e41ebf5de90a85"
|
checksum = "6b7fdd146f86bd90fa2d4cf83a28b45f058e90bcf11ed0cce134e757928771e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1067,6 +1358,15 @@ dependencies = [
|
|||||||
"rand_core 0.3.1",
|
"rand_core 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
@@ -1096,6 +1396,15 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.130"
|
version = "1.0.130"
|
||||||
@@ -1118,9 +1427,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.70"
|
version = "1.0.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
|
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -1148,6 +1457,27 @@ version = "0.4.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
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]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -1155,7 +1485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1226,29 +1556,39 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "time"
|
||||||
version = "1.13.0"
|
version = "0.1.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee"
|
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg 1.0.1",
|
"autocfg 1.0.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio 0.7.14",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.5.1"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095"
|
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1264,6 +1604,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
@@ -1324,6 +1670,17 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
@@ -1336,7 +1693,7 @@ version = "0.2.78"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1361,7 +1718,7 @@ version = "0.4.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
|
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if 1.0.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
@@ -1415,6 +1772,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -1425,6 +1788,12 @@ dependencies = [
|
|||||||
"winapi-x86_64-pc-windows-gnu",
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-build"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -1437,7 +1806,7 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1445,3 +1814,13 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ws2_32-sys"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"winapi-build",
|
||||||
|
]
|
||||||
|
21
Cargo.toml
21
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lazymc"
|
name = "lazymc"
|
||||||
version = "0.2.0"
|
version = "0.2.4"
|
||||||
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
|
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -24,36 +24,43 @@ default = ["rcon", "lobby"]
|
|||||||
# RCON support
|
# RCON support
|
||||||
# Allow use of RCON to manage (stop) server.
|
# Allow use of RCON to manage (stop) server.
|
||||||
# Required on Windows.
|
# Required on Windows.
|
||||||
rcon = ["rust_rcon"]
|
rcon = ["rust_rcon", "async-std"]
|
||||||
|
|
||||||
# Lobby support
|
# Lobby support
|
||||||
# Add lobby join method, keeps client in fake lobby world until server is ready.
|
# 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]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
base64 = "0.13"
|
||||||
bytes = "1.1"
|
bytes = "1.1"
|
||||||
|
chrono = "0.4"
|
||||||
clap = { version = "3.0.0-beta.5", default-features = false, features = [ "std", "cargo", "color", "env", "suggestions", "unicode" ]}
|
clap = { version = "3.0.0-beta.5", default-features = false, features = [ "std", "cargo", "color", "env", "suggestions", "unicode" ]}
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
derive_builder = "0.10"
|
derive_builder = "0.10"
|
||||||
dotenv = "0.15"
|
dotenv = "0.15"
|
||||||
flate2 = { version = "1.0", default-features = false, features = ["default"] }
|
flate2 = { version = "1.0", default-features = false, features = ["default"] }
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false, features = ["executor"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "d26a525" }
|
minecraft-protocol = { git = "https://github.com/timvisee/rust-minecraft-protocol", rev = "356ea54" }
|
||||||
|
notify = "4.0"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.4"
|
||||||
|
proxy-protocol = "0.5"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
shlex = "1.1"
|
shlex = "1.1"
|
||||||
thiserror = "1.0"
|
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"
|
toml = "0.5"
|
||||||
version-compare = "0.1"
|
version-compare = "0.1"
|
||||||
|
|
||||||
# Feature: rcon
|
# 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
|
# Feature: lobby
|
||||||
|
md-5 = { version = "0.9", optional = true }
|
||||||
named-binary-tag = { version = "0.6", optional = true }
|
named-binary-tag = { version = "0.6", optional = true }
|
||||||
quartz_nbt = { version = "0.2", optional = true }
|
quartz_nbt = { version = "0.2", optional = true }
|
||||||
uuid = { version = "0.7", optional = true, features = ["v3"] }
|
uuid = { version = "0.7", optional = true, features = ["v3"] }
|
||||||
|
22
README.md
22
README.md
@@ -35,7 +35,7 @@ https://user-images.githubusercontent.com/856222/141378688-882082be-9efa-4cfe-81
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Very efficient, lightweight & low-profile (~3KB RAM)
|
- 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:
|
- Configure joining client occupation methods:
|
||||||
- Hold: hold clients when server starts, relay when ready, without them noticing
|
- Hold: hold clients when server starts, relay when ready, without them noticing
|
||||||
- Kick: kick clients when server starts, with a starting message
|
- Kick: kick clients when server starts, with a starting message
|
||||||
@@ -43,7 +43,9 @@ 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))_
|
- _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
|
- Customizable MOTD and login messages
|
||||||
- Automatically manages `server.properties` (host, port and RCON settings)
|
- Automatically manages `server.properties` (host, port and RCON settings)
|
||||||
- Graceful server sleep/shutdown through RCON (with `SIGTERM` fallback on Linux/Unix)
|
- Automatically block banned IPs from server within `lazymc`
|
||||||
|
- 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
|
- Restart server on crash
|
||||||
- Lockout mode
|
- Lockout mode
|
||||||
|
|
||||||
@@ -93,20 +95,14 @@ nano lazymc.toml
|
|||||||
lazymc start
|
lazymc start
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you use this in production, please ensure starting and stopping the
|
Please see [extras](./docs/extras.md) for recommendations and additional things
|
||||||
server works as expected by connecting to it once. Watch `lazymc`s output while
|
to set up (e.g. how to fix incorrect client IPs and IP banning on your server).
|
||||||
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.
|
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!
|
||||||
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
|
_Note: If a binary for your system isn't provided, please [compile from
|
||||||
source](#compile-from-source)._
|
source](#compile-from-source). Installation options are limited at this moment. More will be added
|
||||||
|
|
||||||
_Note: Installation options are limited at this moment. More will be added
|
|
||||||
later._
|
later._
|
||||||
|
|
||||||
[latest-release]: https://github.com/timvisee/lazymc/releases/latest
|
[latest-release]: https://github.com/timvisee/lazymc/releases/latest
|
||||||
|
3
TODO.md
3
TODO.md
@@ -9,9 +9,8 @@
|
|||||||
|
|
||||||
- Use server whitelist/blacklist
|
- Use server whitelist/blacklist
|
||||||
- Console error if server already started on port, not through `lazymc`
|
- 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)
|
- 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
|
- Dynamically increase/decrease server polling interval based on server state
|
||||||
- Server polling through query (`enable-query` in `server.properties`, uses GameSpy4 protocol)
|
- 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
|
.\lazymc start
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you use this in production, please ensure starting and stopping the
|
Please see [extras](./extras.md) for recommendations and additional things
|
||||||
server works as expected by connecting to it once. Watch `lazymc`s output while
|
to set up (e.g. how to fix incorrect client IPs and IP banning on your server).
|
||||||
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.
|
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!
|
||||||
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
|
_Note: if you put `lazymc` in `PATH`, or if you
|
||||||
[install](../README.md#compile-from-source) it through Cargo, you can invoke
|
[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 can probably leave the rest as-is.
|
||||||
#
|
#
|
||||||
# You may generate a new configuration with: lazymc config generate
|
# 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]
|
||||||
# Public address. IP and port users connect to.
|
# Public address. IP and port users connect to.
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
# Server version & protocol hint.
|
# Server version & protocol hint.
|
||||||
# Sent to clients until actual server version is known.
|
# Sent to clients until actual server version is known.
|
||||||
# See: https://is.gd/FTQKTP
|
# See: https://git.io/J1Fvx
|
||||||
#version = "1.17.1"
|
#version = "1.17.1"
|
||||||
#protocol = 756
|
#protocol = 756
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
directory = "."
|
directory = "."
|
||||||
|
|
||||||
# Command to start the server.
|
# 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"
|
command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
||||||
|
|
||||||
# Immediately wake server when starting lazymc.
|
# Immediately wake server when starting lazymc.
|
||||||
@@ -43,6 +43,18 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
|||||||
#start_timeout = 300
|
#start_timeout = 300
|
||||||
#stop_timeout = 150
|
#stop_timeout = 150
|
||||||
|
|
||||||
|
# Block banned IPs as listed in banned-ips.json in server directory.
|
||||||
|
#block_banned_ips = true
|
||||||
|
|
||||||
|
# Drop connections from banned IPs.
|
||||||
|
# Banned IPs won't be able to ping or request server status.
|
||||||
|
# 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]
|
[time]
|
||||||
# Sleep after number of seconds.
|
# Sleep after number of seconds.
|
||||||
#sleep_after = 60
|
#sleep_after = 60
|
||||||
@@ -97,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.
|
# The target server will receive original client handshake and login request as received by lazymc.
|
||||||
#address = "127.0.0.1:25565"
|
#address = "127.0.0.1:25565"
|
||||||
|
|
||||||
|
# Add HAProxy v2 header to forwarded connections.
|
||||||
|
# See: https://git.io/J1bYb
|
||||||
|
#send_proxy_v2 = false
|
||||||
|
|
||||||
[join.lobby]
|
[join.lobby]
|
||||||
# Lobby occupation method.
|
# Lobby occupation method.
|
||||||
# The client joins a fake lobby server with an empty world, floating in space.
|
# The client joins a fake lobby server with an empty world, floating in space.
|
||||||
@@ -133,7 +149,7 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
|||||||
[rcon]
|
[rcon]
|
||||||
# Enable sleeping server through RCON.
|
# Enable sleeping server through RCON.
|
||||||
# Must be enabled on Windows.
|
# Must be enabled on Windows.
|
||||||
#enabled = true
|
#enabled = false # default: false, true on Windows
|
||||||
|
|
||||||
# Server RCON port. Must differ from public and server port.
|
# Server RCON port. Must differ from public and server port.
|
||||||
#port = 25575
|
#port = 25575
|
||||||
@@ -143,6 +159,10 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
|||||||
#password = ""
|
#password = ""
|
||||||
#randomize_password = true
|
#randomize_password = true
|
||||||
|
|
||||||
|
# Add HAProxy v2 header to RCON connections.
|
||||||
|
# See: https://git.io/J1bYb
|
||||||
|
#send_proxy_v2 = false
|
||||||
|
|
||||||
[advanced]
|
[advanced]
|
||||||
# Automatically update values in Minecraft server.properties file as required.
|
# Automatically update values in Minecraft server.properties file as required.
|
||||||
#rewrite_server_properties = true
|
#rewrite_server_properties = true
|
||||||
@@ -150,4 +170,4 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
|||||||
[config]
|
[config]
|
||||||
# lazymc version this configuration is for.
|
# lazymc version this configuration is for.
|
||||||
# Don't change unless you know what you're doing.
|
# Don't change unless you know what you're doing.
|
||||||
version = "0.2.0"
|
version = "0.2.4"
|
||||||
|
BIN
res/unknown_server.png
Normal file
BIN
res/unknown_server.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
res/unknown_server_optimized.png
Normal file
BIN
res/unknown_server_optimized.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use crate::config::{self, Config};
|
use crate::config::{self, Config, Server as ConfigServer};
|
||||||
use crate::mc::server_properties;
|
use crate::mc::server_properties;
|
||||||
use crate::proto;
|
use crate::proto;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
@@ -120,7 +120,7 @@ fn rewrite_server_properties(config: &Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure server directory is set, it must exist
|
// Ensure server directory is set, it must exist
|
||||||
let dir = match &config.server.directory {
|
let dir = match ConfigServer::server_directory(config) {
|
||||||
Some(dir) => dir,
|
Some(dir) => dir,
|
||||||
None => {
|
None => {
|
||||||
warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE);
|
warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -9,12 +9,13 @@ use version_compare::Cmp;
|
|||||||
|
|
||||||
use crate::proto;
|
use crate::proto;
|
||||||
use crate::util::error::{quit_error, quit_error_msg, ErrorHintsBuilder};
|
use crate::util::error::{quit_error, quit_error_msg, ErrorHintsBuilder};
|
||||||
|
use crate::util::serde::to_socket_addrs;
|
||||||
|
|
||||||
/// Default configuration file location.
|
/// Default configuration file location.
|
||||||
pub const CONFIG_FILE: &str = "lazymc.toml";
|
pub const CONFIG_FILE: &str = "lazymc.toml";
|
||||||
|
|
||||||
/// Configuration version user should be using, or warning will be shown.
|
/// Configuration version user should be using, or warning will be shown.
|
||||||
const CONFIG_VERSION: &str = "0.2.0";
|
const CONFIG_VERSION: &str = "0.2.1";
|
||||||
|
|
||||||
/// Load config from file, based on CLI arguments.
|
/// Load config from file, based on CLI arguments.
|
||||||
///
|
///
|
||||||
@@ -62,6 +63,12 @@ pub fn load(matches: &ArgMatches) -> Config {
|
|||||||
/// Configuration.
|
/// Configuration.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// Configuration path if known.
|
||||||
|
///
|
||||||
|
/// Should be used as base directory for filesystem operations.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Public configuration.
|
/// Public configuration.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub public: Public,
|
pub public: Public,
|
||||||
@@ -100,9 +107,9 @@ pub struct Config {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Load configuration from file.
|
/// Load configuration from file.
|
||||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
|
pub fn load(path: PathBuf) -> Result<Self, io::Error> {
|
||||||
let data = fs::read(path)?;
|
let data = fs::read(&path)?;
|
||||||
let config: Config = toml::from_slice(&data)?;
|
let mut config: Config = toml::from_slice(&data)?;
|
||||||
|
|
||||||
// Show warning if config version is problematic
|
// Show warning if config version is problematic
|
||||||
match &config.config.version {
|
match &config.config.version {
|
||||||
@@ -117,6 +124,7 @@ impl Config {
|
|||||||
Ok(true) => {}
|
Ok(true) => {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
config.path.replace(path);
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
@@ -127,6 +135,7 @@ impl Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Public {
|
pub struct Public {
|
||||||
/// Public address.
|
/// Public address.
|
||||||
|
#[serde(deserialize_with = "to_socket_addrs")]
|
||||||
pub address: SocketAddr,
|
pub address: SocketAddr,
|
||||||
|
|
||||||
/// Minecraft protocol version name hint.
|
/// Minecraft protocol version name hint.
|
||||||
@@ -150,14 +159,19 @@ impl Default for Public {
|
|||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
/// Server directory.
|
/// Server directory.
|
||||||
|
///
|
||||||
|
/// Private because you should use `Server::server_directory()` instead.
|
||||||
#[serde(default = "option_pathbuf_dot")]
|
#[serde(default = "option_pathbuf_dot")]
|
||||||
pub directory: Option<PathBuf>,
|
directory: Option<PathBuf>,
|
||||||
|
|
||||||
/// Start command.
|
/// Start command.
|
||||||
pub command: String,
|
pub command: String,
|
||||||
|
|
||||||
/// Server address.
|
/// Server address.
|
||||||
#[serde(default = "server_address_default")]
|
#[serde(
|
||||||
|
deserialize_with = "to_socket_addrs",
|
||||||
|
default = "server_address_default"
|
||||||
|
)]
|
||||||
pub address: SocketAddr,
|
pub address: SocketAddr,
|
||||||
|
|
||||||
/// Immediately wake server when starting lazymc.
|
/// Immediately wake server when starting lazymc.
|
||||||
@@ -175,6 +189,31 @@ pub struct Server {
|
|||||||
/// Server stopping timeout. Force kill server process if it takes longer.
|
/// Server stopping timeout. Force kill server process if it takes longer.
|
||||||
#[serde(default = "u32_150")]
|
#[serde(default = "u32_150")]
|
||||||
pub stop_timeout: u32,
|
pub stop_timeout: u32,
|
||||||
|
|
||||||
|
/// Block banned IPs as listed in banned-ips.json in server directory.
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub block_banned_ips: bool,
|
||||||
|
|
||||||
|
/// 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.
|
/// Time configuration.
|
||||||
@@ -318,13 +357,19 @@ impl Default for JoinHold {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct JoinForward {
|
pub struct JoinForward {
|
||||||
/// IP and port to forward to.
|
/// IP and port to forward to.
|
||||||
|
#[serde(deserialize_with = "to_socket_addrs")]
|
||||||
pub address: SocketAddr,
|
pub address: SocketAddr,
|
||||||
|
|
||||||
|
/// Add HAProxy v2 header to proxied connections.
|
||||||
|
#[serde(default)]
|
||||||
|
pub send_proxy_v2: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JoinForward {
|
impl Default for JoinForward {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
address: "127.0.0.1:25565".parse().unwrap(),
|
address: "127.0.0.1:25565".parse().unwrap(),
|
||||||
|
send_proxy_v2: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,15 +432,19 @@ pub struct Rcon {
|
|||||||
|
|
||||||
/// Randomize server RCON password on each start.
|
/// Randomize server RCON password on each start.
|
||||||
pub randomize_password: bool,
|
pub randomize_password: bool,
|
||||||
|
|
||||||
|
/// Add HAProxy v2 header to RCON connections.
|
||||||
|
pub send_proxy_v2: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rcon {
|
impl Default for Rcon {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: cfg!(windows),
|
||||||
port: 25575,
|
port: 25575,
|
||||||
password: "".into(),
|
password: "".into(),
|
||||||
randomize_password: true,
|
randomize_password: true,
|
||||||
|
send_proxy_v2: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,3 +494,7 @@ fn u32_300() -> u32 {
|
|||||||
fn u32_150() -> u32 {
|
fn u32_150() -> u32 {
|
||||||
300
|
300
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bool_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
32
src/join/forward.rs
Normal file
32
src/join/forward.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::proxy::ProxyHeader;
|
||||||
|
use crate::service;
|
||||||
|
|
||||||
|
use super::MethodResult;
|
||||||
|
|
||||||
|
/// Forward the client.
|
||||||
|
pub async fn occupy(
|
||||||
|
config: Arc<Config>,
|
||||||
|
inbound: TcpStream,
|
||||||
|
inbound_history: &mut BytesMut,
|
||||||
|
) -> Result<MethodResult, ()> {
|
||||||
|
trace!(target: "lazymc", "Using forward method to occupy joining client");
|
||||||
|
|
||||||
|
debug!(target: "lazymc", "Forwarding client to {:?}!", config.join.forward.address);
|
||||||
|
|
||||||
|
service::server::route_proxy_address_queue(
|
||||||
|
inbound,
|
||||||
|
ProxyHeader::Proxy.not_none(config.join.forward.send_proxy_v2),
|
||||||
|
config.join.forward.address,
|
||||||
|
inbound_history.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: do not consume, continue on proxy connect failure
|
||||||
|
|
||||||
|
Ok(MethodResult::Consumed)
|
||||||
|
}
|
101
src/join/hold.rs
Normal file
101
src/join/hold.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::server::{Server, State};
|
||||||
|
use crate::service;
|
||||||
|
|
||||||
|
use super::MethodResult;
|
||||||
|
|
||||||
|
/// Hold the client.
|
||||||
|
pub async fn occupy(
|
||||||
|
config: Arc<Config>,
|
||||||
|
server: Arc<Server>,
|
||||||
|
inbound: TcpStream,
|
||||||
|
inbound_history: &mut BytesMut,
|
||||||
|
) -> Result<MethodResult, ()> {
|
||||||
|
trace!(target: "lazymc", "Using hold method to occupy joining client");
|
||||||
|
|
||||||
|
// Server must be starting
|
||||||
|
if server.state() != State::Starting {
|
||||||
|
return Ok(MethodResult::Continue(inbound));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start holding, consume client
|
||||||
|
if hold(&config, &server).await? {
|
||||||
|
service::server::route_proxy_queue(inbound, config, inbound_history.clone());
|
||||||
|
return Ok(MethodResult::Consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MethodResult::Continue(inbound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hold a client while server starts.
|
||||||
|
///
|
||||||
|
/// Returns holding status. `true` if client is held and it should be proxied, `false` it was held
|
||||||
|
/// but it timed out.
|
||||||
|
async fn hold<'a>(config: &Config, server: &Server) -> Result<bool, ()> {
|
||||||
|
trace!(target: "lazymc", "Started holding client");
|
||||||
|
|
||||||
|
// A task to wait for suitable server state
|
||||||
|
// Waits for started state, errors if stopping/stopped state is reached
|
||||||
|
let task_wait = async {
|
||||||
|
let mut state = server.state_receiver();
|
||||||
|
loop {
|
||||||
|
// Wait for state change
|
||||||
|
state.changed().await.unwrap();
|
||||||
|
|
||||||
|
match state.borrow().deref() {
|
||||||
|
// Still waiting on server start
|
||||||
|
State::Starting => {
|
||||||
|
trace!(target: "lazymc", "Server not ready, holding client for longer");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server started, start relaying and proxy
|
||||||
|
State::Started => {
|
||||||
|
break true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server stopping, this shouldn't happen, kick
|
||||||
|
State::Stopping => {
|
||||||
|
warn!(target: "lazymc", "Server stopping for held client, disconnecting");
|
||||||
|
break false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server stopped, this shouldn't happen, disconnect
|
||||||
|
State::Stopped => {
|
||||||
|
error!(target: "lazymc", "Server stopped for held client, disconnecting");
|
||||||
|
break false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for server state with timeout
|
||||||
|
let timeout = Duration::from_secs(config.join.hold.timeout as u64);
|
||||||
|
match time::timeout(timeout, task_wait).await {
|
||||||
|
// Relay client to proxy
|
||||||
|
Ok(true) => {
|
||||||
|
info!(target: "lazymc", "Server ready for held client, relaying to server");
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server stopping/stopped, this shouldn't happen, kick
|
||||||
|
Ok(false) => {
|
||||||
|
warn!(target: "lazymc", "Server stopping for held client");
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout reached, kick with starting message
|
||||||
|
Err(_) => {
|
||||||
|
warn!(target: "lazymc", "Held client reached timeout of {}s", config.join.hold.timeout);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/join/kick.rs
Normal file
33
src/join/kick.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::net;
|
||||||
|
use crate::proto::action;
|
||||||
|
use crate::proto::client::Client;
|
||||||
|
use crate::server::{self, Server};
|
||||||
|
|
||||||
|
use super::MethodResult;
|
||||||
|
|
||||||
|
/// Kick the client.
|
||||||
|
pub async fn occupy(
|
||||||
|
client: &Client,
|
||||||
|
config: &Config,
|
||||||
|
server: &Server,
|
||||||
|
mut inbound: TcpStream,
|
||||||
|
) -> Result<MethodResult, ()> {
|
||||||
|
trace!(target: "lazymc", "Using kick method to occupy joining client");
|
||||||
|
|
||||||
|
// Select message and kick
|
||||||
|
let msg = match server.state() {
|
||||||
|
server::State::Starting | server::State::Stopped | server::State::Started => {
|
||||||
|
&config.join.kick.starting
|
||||||
|
}
|
||||||
|
server::State::Stopping => &config.join.kick.stopping,
|
||||||
|
};
|
||||||
|
action::kick(client, msg, &mut inbound.split().1).await?;
|
||||||
|
|
||||||
|
// Gracefully close connection
|
||||||
|
net::close_tcp_stream(inbound).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(MethodResult::Consumed)
|
||||||
|
}
|
30
src/join/lobby.rs
Normal file
30
src/join/lobby.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::lobby;
|
||||||
|
use crate::proto::client::{Client, ClientInfo};
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
use super::MethodResult;
|
||||||
|
|
||||||
|
/// Lobby the client.
|
||||||
|
pub async fn occupy(
|
||||||
|
client: &Client,
|
||||||
|
client_info: ClientInfo,
|
||||||
|
config: Arc<Config>,
|
||||||
|
server: Arc<Server>,
|
||||||
|
inbound: TcpStream,
|
||||||
|
inbound_queue: BytesMut,
|
||||||
|
) -> Result<MethodResult, ()> {
|
||||||
|
trace!(target: "lazymc", "Using lobby method to occupy joining client");
|
||||||
|
|
||||||
|
// Start lobby
|
||||||
|
lobby::serve(client, client_info, inbound, config, server, inbound_queue).await?;
|
||||||
|
|
||||||
|
// TODO: do not consume client here, allow other join method on fail
|
||||||
|
|
||||||
|
Ok(MethodResult::Consumed)
|
||||||
|
}
|
106
src/join/mod.rs
Normal file
106
src/join/mod.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::net;
|
||||||
|
use crate::proto::client::{Client, ClientInfo, ClientState};
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub mod forward;
|
||||||
|
pub mod hold;
|
||||||
|
pub mod kick;
|
||||||
|
#[cfg(feature = "lobby")]
|
||||||
|
pub mod lobby;
|
||||||
|
|
||||||
|
/// A result returned by a join occupy method.
|
||||||
|
pub enum MethodResult {
|
||||||
|
/// Client is consumed.
|
||||||
|
Consumed,
|
||||||
|
|
||||||
|
/// Method is done, continue with the next.
|
||||||
|
Continue(TcpStream),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start occupying client.
|
||||||
|
///
|
||||||
|
/// This assumes the login start packet has just been received.
|
||||||
|
pub async fn occupy(
|
||||||
|
client: Client,
|
||||||
|
#[allow(unused_variables)] client_info: ClientInfo,
|
||||||
|
config: Arc<Config>,
|
||||||
|
server: Arc<Server>,
|
||||||
|
mut inbound: TcpStream,
|
||||||
|
mut inbound_history: BytesMut,
|
||||||
|
#[allow(unused_variables)] login_queue: BytesMut,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
// Assert state is correct
|
||||||
|
assert_eq!(
|
||||||
|
client.state(),
|
||||||
|
ClientState::Login,
|
||||||
|
"when occupying client, it should be in login state"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go through all configured join methods
|
||||||
|
for method in &config.join.methods {
|
||||||
|
// Invoke method, take result
|
||||||
|
let result = match method {
|
||||||
|
// Kick method, immediately kick client
|
||||||
|
Method::Kick => kick::occupy(&client, &config, &server, inbound).await?,
|
||||||
|
|
||||||
|
// Hold method, hold client connection while server starts
|
||||||
|
Method::Hold => {
|
||||||
|
hold::occupy(
|
||||||
|
config.clone(),
|
||||||
|
server.clone(),
|
||||||
|
inbound,
|
||||||
|
&mut inbound_history,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward method, forward client connection while server starts
|
||||||
|
Method::Forward => {
|
||||||
|
forward::occupy(config.clone(), inbound, &mut inbound_history).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lobby method, keep client in lobby while server starts
|
||||||
|
#[cfg(feature = "lobby")]
|
||||||
|
Method::Lobby => {
|
||||||
|
lobby::occupy(
|
||||||
|
&client,
|
||||||
|
client_info.clone(),
|
||||||
|
config.clone(),
|
||||||
|
server.clone(),
|
||||||
|
inbound,
|
||||||
|
login_queue.clone(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lobby method, keep client in lobby while server starts
|
||||||
|
#[cfg(not(feature = "lobby"))]
|
||||||
|
Method::Lobby => {
|
||||||
|
error!(target: "lazymc", "Lobby join method not supported in this lazymc build");
|
||||||
|
MethodResult::Continue(inbound)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle method result
|
||||||
|
match result {
|
||||||
|
MethodResult::Consumed => return Ok(()),
|
||||||
|
MethodResult::Continue(stream) => {
|
||||||
|
inbound = stream;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(target: "lazymc", "No method left to occupy joining client, disconnecting");
|
||||||
|
|
||||||
|
// Gracefully close connection
|
||||||
|
net::close_tcp_stream(inbound).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
488
src/lobby.rs
488
src/lobby.rs
@@ -8,24 +8,25 @@ use bytes::BytesMut;
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use minecraft_protocol::data::chat::{Message, Payload};
|
use minecraft_protocol::data::chat::{Message, Payload};
|
||||||
use minecraft_protocol::decoder::Decoder;
|
use minecraft_protocol::decoder::Decoder;
|
||||||
use minecraft_protocol::encoder::Encoder;
|
|
||||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||||
use minecraft_protocol::version::v1_14_4::login::{LoginStart, LoginSuccess, SetCompression};
|
use minecraft_protocol::version::v1_14_4::login::{LoginStart, LoginSuccess, SetCompression};
|
||||||
use minecraft_protocol::version::v1_17_1::game::{
|
use minecraft_protocol::version::v1_17_1::game::{
|
||||||
ClientBoundKeepAlive, JoinGame, NamedSoundEffect, PlayerPositionAndLook, PluginMessage,
|
ClientBoundKeepAlive, ClientBoundPluginMessage, JoinGame, NamedSoundEffect,
|
||||||
Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
|
PlayerPositionAndLook, Respawn, SetTitleSubtitle, SetTitleText, SetTitleTimes, TimeUpdate,
|
||||||
};
|
};
|
||||||
use nbt::CompoundTag;
|
use nbt::CompoundTag;
|
||||||
use tokio::io::{self, AsyncWriteExt};
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::config::*;
|
use crate::config::*;
|
||||||
use crate::mc;
|
use crate::mc::{self, uuid};
|
||||||
use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket};
|
use crate::net;
|
||||||
|
use crate::proto;
|
||||||
|
use crate::proto::client::{Client, ClientInfo, ClientState};
|
||||||
|
use crate::proto::{packet, packets};
|
||||||
use crate::proxy;
|
use crate::proxy;
|
||||||
use crate::server::{Server, State};
|
use crate::server::{Server, State};
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ const SERVER_BRAND: &[u8] = b"lazymc";
|
|||||||
// TODO: do not drop error here, return Box<dyn Error>
|
// TODO: do not drop error here, return Box<dyn Error>
|
||||||
// TODO: on error, nicely kick client with message
|
// TODO: on error, nicely kick client with message
|
||||||
pub async fn serve(
|
pub async fn serve(
|
||||||
client: Client,
|
client: &Client,
|
||||||
client_info: ClientInfo,
|
client_info: ClientInfo,
|
||||||
mut inbound: TcpStream,
|
mut inbound: TcpStream,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
@@ -88,7 +89,7 @@ pub async fn serve(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, _raw) = match proto::read_packet(&client, &mut inbound_buf, &mut reader).await
|
let (packet, _raw) = match packet::read_packet(client, &mut inbound_buf, &mut reader).await
|
||||||
{
|
{
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
@@ -102,9 +103,7 @@ pub async fn serve(
|
|||||||
let client_state = client.state();
|
let client_state = client.state();
|
||||||
|
|
||||||
// Hijack login start
|
// Hijack login start
|
||||||
if client_state == ClientState::Login
|
if client_state == ClientState::Login && packet.id == packets::login::SERVER_LOGIN_START {
|
||||||
&& packet.id == proto::packets::login::SERVER_LOGIN_START
|
|
||||||
{
|
|
||||||
// Parse login start packet
|
// Parse login start packet
|
||||||
let login_start = LoginStart::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
let login_start = LoginStart::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||||
|
|
||||||
@@ -113,33 +112,36 @@ pub async fn serve(
|
|||||||
// Respond with set compression if compression is enabled based on threshold
|
// Respond with set compression if compression is enabled based on threshold
|
||||||
if proto::COMPRESSION_THRESHOLD >= 0 {
|
if proto::COMPRESSION_THRESHOLD >= 0 {
|
||||||
trace!(target: "lazymc::lobby", "Enabling compression for lobby client because server has it enabled (threshold: {})", proto::COMPRESSION_THRESHOLD);
|
trace!(target: "lazymc::lobby", "Enabling compression for lobby client because server has it enabled (threshold: {})", proto::COMPRESSION_THRESHOLD);
|
||||||
respond_set_compression(&client, &mut writer, proto::COMPRESSION_THRESHOLD).await?;
|
respond_set_compression(client, &mut writer, proto::COMPRESSION_THRESHOLD).await?;
|
||||||
client.set_compression(proto::COMPRESSION_THRESHOLD);
|
client.set_compression(proto::COMPRESSION_THRESHOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with login success, switch to play state
|
// Respond with login success, switch to play state
|
||||||
respond_login_success(&client, &mut writer, &login_start).await?;
|
respond_login_success(client, &mut writer, &login_start).await?;
|
||||||
client.set_state(ClientState::Play);
|
client.set_state(ClientState::Play);
|
||||||
|
|
||||||
trace!(target: "lazymc::lobby", "Client login success, sending required play packets for lobby world");
|
trace!(target: "lazymc::lobby", "Client login success, sending required play packets for lobby world");
|
||||||
|
|
||||||
// Send packets to client required to get into workable play state for lobby world
|
// Send packets to client required to get into workable play state for lobby world
|
||||||
send_lobby_play_packets(&client, &mut writer, &server).await?;
|
send_lobby_play_packets(client, &mut writer, &server).await?;
|
||||||
|
|
||||||
// Wait for server to come online, then set up new connection to it
|
// Wait for server to come online, then set up new connection to it
|
||||||
stage_wait(&client, &server, &config, &mut writer).await?;
|
stage_wait(client, &server, &config, &mut writer).await?;
|
||||||
let (server_client, mut outbound, mut server_buf) =
|
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
|
// Grab join game packet from server
|
||||||
let join_game =
|
let join_game =
|
||||||
wait_for_server_join_game(&server_client, &mut outbound, &mut server_buf).await?;
|
wait_for_server_join_game(&server_client, &mut outbound, &mut server_buf).await?;
|
||||||
|
|
||||||
// Reset lobby title
|
// Reset lobby title
|
||||||
send_lobby_title(&client, &mut writer, "").await?;
|
send_lobby_title(client, &mut writer, "").await?;
|
||||||
|
|
||||||
// Play ready sound if configured
|
// Play ready sound if configured
|
||||||
play_lobby_ready_sound(&client, &mut writer, &config).await?;
|
play_lobby_ready_sound(client, &mut writer, &config).await?;
|
||||||
|
|
||||||
// Wait a second because Notchian servers are slow
|
// Wait a second because Notchian servers are slow
|
||||||
// See: https://wiki.vg/Protocol#Login_Success
|
// See: https://wiki.vg/Protocol#Login_Success
|
||||||
@@ -147,7 +149,7 @@ pub async fn serve(
|
|||||||
time::sleep(SERVER_WARMUP).await;
|
time::sleep(SERVER_WARMUP).await;
|
||||||
|
|
||||||
// Send respawn packet, initiates teleport to real server world
|
// Send respawn packet, initiates teleport to real server world
|
||||||
send_respawn_from_join(&client, &mut writer, join_game).await?;
|
send_respawn_from_join(client, &mut writer, join_game).await?;
|
||||||
|
|
||||||
// Drain inbound connection so we don't confuse the server
|
// Drain inbound connection so we don't confuse the server
|
||||||
// TODO: can we void everything? we might need to forward everything to server except
|
// TODO: can we void everything? we might need to forward everything to server except
|
||||||
@@ -172,11 +174,7 @@ pub async fn serve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gracefully close connection
|
// Gracefully close connection
|
||||||
match writer.shutdown().await {
|
net::close_tcp_stream(inbound).await.map_err(|_| ())?;
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
|
||||||
Err(_) => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -187,16 +185,7 @@ async fn respond_set_compression(
|
|||||||
writer: &mut WriteHalf<'_>,
|
writer: &mut WriteHalf<'_>,
|
||||||
threshold: i32,
|
threshold: i32,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let packet = SetCompression { threshold };
|
packet::write_packet(SetCompression { threshold }, client, writer).await
|
||||||
|
|
||||||
let mut data = Vec::new();
|
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
|
||||||
|
|
||||||
let response =
|
|
||||||
RawPacket::new(proto::packets::login::CLIENT_SET_COMPRESSION, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Respond to client with login success packet
|
/// Respond to client with login success packet
|
||||||
@@ -206,22 +195,15 @@ async fn respond_login_success(
|
|||||||
writer: &mut WriteHalf<'_>,
|
writer: &mut WriteHalf<'_>,
|
||||||
login_start: &LoginStart,
|
login_start: &LoginStart,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let packet = LoginSuccess {
|
packet::write_packet(
|
||||||
uuid: Uuid::new_v3(
|
LoginSuccess {
|
||||||
&Uuid::new_v3(&Uuid::nil(), b"OfflinePlayer"),
|
uuid: uuid::offline_player_uuid(&login_start.name),
|
||||||
login_start.name.as_bytes(),
|
username: login_start.name.clone(),
|
||||||
),
|
},
|
||||||
username: login_start.name.clone(),
|
client,
|
||||||
};
|
writer,
|
||||||
|
)
|
||||||
let mut data = Vec::new();
|
.await
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
|
||||||
|
|
||||||
let response =
|
|
||||||
RawPacket::new(proto::packets::login::CLIENT_LOGIN_SUCCESS, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Play lobby ready sound effect if configured.
|
/// Play lobby ready sound effect if configured.
|
||||||
@@ -275,86 +257,75 @@ async fn send_lobby_join_game(
|
|||||||
server: &Server,
|
server: &Server,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
// Send Minecrafts default states, slightly customised for lobby world
|
// Send Minecrafts default states, slightly customised for lobby world
|
||||||
let packet = {
|
packet::write_packet(
|
||||||
let status = server.status().await;
|
{
|
||||||
|
let status = server.status().await;
|
||||||
|
|
||||||
JoinGame {
|
JoinGame {
|
||||||
// Player ID must be unique, if it collides with another server entity ID the player gets
|
// Player ID must be unique, if it collides with another server entity ID the player gets
|
||||||
// in a weird state and cannot move
|
// in a weird state and cannot move
|
||||||
entity_id: 0,
|
entity_id: 0,
|
||||||
// TODO: use real server value
|
// TODO: use real server value
|
||||||
hardcore: false,
|
hardcore: false,
|
||||||
game_mode: 3,
|
game_mode: 3,
|
||||||
previous_game_mode: -1i8 as u8,
|
previous_game_mode: -1i8 as u8,
|
||||||
world_names: vec![
|
world_names: vec![
|
||||||
"minecraft:overworld".into(),
|
"minecraft:overworld".into(),
|
||||||
"minecraft:the_nether".into(),
|
"minecraft:the_nether".into(),
|
||||||
"minecraft:the_end".into(),
|
"minecraft:the_end".into(),
|
||||||
],
|
],
|
||||||
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
|
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
|
||||||
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
|
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
|
||||||
world_name: "lazymc:lobby".into(),
|
world_name: "lazymc:lobby".into(),
|
||||||
hashed_seed: 0,
|
hashed_seed: 0,
|
||||||
max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20),
|
max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20),
|
||||||
// TODO: use real server value
|
// TODO: use real server value
|
||||||
view_distance: 10,
|
view_distance: 10,
|
||||||
// TODO: use real server value
|
// TODO: use real server value
|
||||||
reduced_debug_info: false,
|
reduced_debug_info: false,
|
||||||
// TODO: use real server value
|
// TODO: use real server value
|
||||||
enable_respawn_screen: true,
|
enable_respawn_screen: true,
|
||||||
is_debug: true,
|
is_debug: true,
|
||||||
is_flat: false,
|
is_flat: false,
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
client,
|
||||||
let mut data = Vec::new();
|
writer,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
)
|
||||||
|
.await
|
||||||
let response = RawPacket::new(proto::packets::play::CLIENT_JOIN_GAME, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby brand to client.
|
/// Send lobby brand to client.
|
||||||
async fn send_lobby_brand(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_lobby_brand(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||||
let packet = PluginMessage {
|
packet::write_packet(
|
||||||
channel: "minecraft:brand".into(),
|
ClientBoundPluginMessage {
|
||||||
data: SERVER_BRAND.into(),
|
channel: "minecraft:brand".into(),
|
||||||
};
|
data: SERVER_BRAND.into(),
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await
|
||||||
RawPacket::new(proto::packets::play::CLIENT_PLUGIN_MESSAGE, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby player position to client.
|
/// Send lobby player position to client.
|
||||||
async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_lobby_player_pos(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||||
// Send player location, disables download terrain screen
|
// Send player location, disables download terrain screen
|
||||||
let packet = PlayerPositionAndLook {
|
packet::write_packet(
|
||||||
x: 0.0,
|
PlayerPositionAndLook {
|
||||||
y: 0.0,
|
x: 0.0,
|
||||||
z: 0.0,
|
y: 0.0,
|
||||||
yaw: 0.0,
|
z: 0.0,
|
||||||
pitch: 90.0,
|
yaw: 0.0,
|
||||||
flags: 0b00000000,
|
pitch: 90.0,
|
||||||
teleport_id: 0,
|
flags: 0b00000000,
|
||||||
dismount_vehicle: true,
|
teleport_id: 0,
|
||||||
};
|
dismount_vehicle: true,
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await
|
||||||
RawPacket::new(proto::packets::play::CLIENT_PLAYER_POS_LOOK, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby time update to client.
|
/// Send lobby time update to client.
|
||||||
@@ -362,38 +333,32 @@ async fn send_lobby_time_update(client: &Client, writer: &mut WriteHalf<'_>) ->
|
|||||||
const MC_TIME_NOON: i64 = 6000;
|
const MC_TIME_NOON: i64 = 6000;
|
||||||
|
|
||||||
// Send time update, required once for keep-alive packets
|
// Send time update, required once for keep-alive packets
|
||||||
let packet = TimeUpdate {
|
packet::write_packet(
|
||||||
world_age: MC_TIME_NOON,
|
TimeUpdate {
|
||||||
time_of_day: MC_TIME_NOON,
|
world_age: MC_TIME_NOON,
|
||||||
};
|
time_of_day: MC_TIME_NOON,
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response = RawPacket::new(proto::packets::play::CLIENT_TIME_UPDATE, data).encode(client)?;
|
.await
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send keep alive packet to client.
|
/// Send keep alive packet to client.
|
||||||
///
|
///
|
||||||
/// Required periodically in play mode to prevent client timeout.
|
/// Required periodically in play mode to prevent client timeout.
|
||||||
async fn send_keep_alive(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_keep_alive(client: &Client, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||||
let packet = ClientBoundKeepAlive {
|
packet::write_packet(
|
||||||
// Keep sending new IDs
|
ClientBoundKeepAlive {
|
||||||
id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed),
|
// Keep sending new IDs
|
||||||
};
|
id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed),
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response = RawPacket::new(proto::packets::play::CLIENT_KEEP_ALIVE, data).encode(client)?;
|
.await
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
// TODO: verify we receive keep alive response with same ID from client
|
// TODO: verify we receive keep alive response with same ID from client
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby title packets to client.
|
/// Send lobby title packets to client.
|
||||||
@@ -411,53 +376,45 @@ async fn send_lobby_title(
|
|||||||
let subtitle = text.lines().skip(1).collect::<Vec<_>>().join("\n");
|
let subtitle = text.lines().skip(1).collect::<Vec<_>>().join("\n");
|
||||||
|
|
||||||
// Set title
|
// Set title
|
||||||
let packet = SetTitleText {
|
packet::write_packet(
|
||||||
text: Message::new(Payload::text(title)),
|
SetTitleText {
|
||||||
};
|
text: Message::new(Payload::text(title)),
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await?;
|
||||||
RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_TEXT, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
// Set subtitle
|
// Set subtitle
|
||||||
let packet = SetTitleSubtitle {
|
packet::write_packet(
|
||||||
text: Message::new(Payload::text(&subtitle)),
|
SetTitleSubtitle {
|
||||||
};
|
text: Message::new(Payload::text(&subtitle)),
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await?;
|
||||||
RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_SUBTITLE, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
// Set title times
|
// Set title times
|
||||||
let packet = if title.is_empty() && subtitle.is_empty() {
|
packet::write_packet(
|
||||||
// Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
|
if title.is_empty() && subtitle.is_empty() {
|
||||||
SetTitleTimes {
|
// Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
|
||||||
fade_in: 10,
|
SetTitleTimes {
|
||||||
stay: 70,
|
fade_in: 10,
|
||||||
fade_out: 20,
|
stay: 70,
|
||||||
}
|
fade_out: 20,
|
||||||
} else {
|
}
|
||||||
SetTitleTimes {
|
} else {
|
||||||
fade_in: 0,
|
SetTitleTimes {
|
||||||
stay: KEEP_ALIVE_INTERVAL.as_secs() as i32 * mc::TICKS_PER_SECOND as i32 * 2,
|
fade_in: 0,
|
||||||
fade_out: 0,
|
stay: KEEP_ALIVE_INTERVAL.as_secs() as i32 * mc::TICKS_PER_SECOND as i32 * 2,
|
||||||
}
|
fade_out: 0,
|
||||||
};
|
}
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await
|
||||||
RawPacket::new(proto::packets::play::CLIENT_SET_TITLE_TIMES, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby ready sound effect to client.
|
/// Send lobby ready sound effect to client.
|
||||||
@@ -466,24 +423,20 @@ async fn send_lobby_sound_effect(
|
|||||||
writer: &mut WriteHalf<'_>,
|
writer: &mut WriteHalf<'_>,
|
||||||
sound_name: &str,
|
sound_name: &str,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let packet = NamedSoundEffect {
|
packet::write_packet(
|
||||||
sound_name: sound_name.into(),
|
NamedSoundEffect {
|
||||||
sound_category: 0,
|
sound_name: sound_name.into(),
|
||||||
effect_pos_x: 0,
|
sound_category: 0,
|
||||||
effect_pos_y: 0,
|
effect_pos_x: 0,
|
||||||
effect_pos_z: 0,
|
effect_pos_y: 0,
|
||||||
volume: 1.0,
|
effect_pos_z: 0,
|
||||||
pitch: 1.0,
|
volume: 1.0,
|
||||||
};
|
pitch: 1.0,
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response =
|
.await
|
||||||
RawPacket::new(proto::packets::play::CLIENT_NAMED_SOUND_EFFECT, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send respawn packet to client to jump from lobby into now loaded server.
|
/// Send respawn packet to client to jump from lobby into now loaded server.
|
||||||
@@ -494,24 +447,21 @@ async fn send_respawn_from_join(
|
|||||||
writer: &mut WriteHalf<'_>,
|
writer: &mut WriteHalf<'_>,
|
||||||
join_game: JoinGame,
|
join_game: JoinGame,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let packet = Respawn {
|
packet::write_packet(
|
||||||
dimension: join_game.dimension,
|
Respawn {
|
||||||
world_name: join_game.world_name,
|
dimension: join_game.dimension,
|
||||||
hashed_seed: join_game.hashed_seed,
|
world_name: join_game.world_name,
|
||||||
game_mode: join_game.game_mode,
|
hashed_seed: join_game.hashed_seed,
|
||||||
previous_game_mode: join_game.previous_game_mode,
|
game_mode: join_game.game_mode,
|
||||||
is_debug: join_game.is_debug,
|
previous_game_mode: join_game.previous_game_mode,
|
||||||
is_flat: join_game.is_flat,
|
is_debug: join_game.is_debug,
|
||||||
copy_metadata: false,
|
is_flat: join_game.is_flat,
|
||||||
};
|
copy_metadata: false,
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
writer,
|
||||||
|
)
|
||||||
let response = RawPacket::new(proto::packets::play::CLIENT_RESPAWN, data).encode(client)?;
|
.await
|
||||||
writer.write_all(&response).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An infinite keep-alive loop.
|
/// An infinite keep-alive loop.
|
||||||
@@ -612,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.
|
/// This will initialize the connection to the play state. Client details are used.
|
||||||
async fn connect_to_server(
|
async fn connect_to_server(
|
||||||
client_info: ClientInfo,
|
client_info: ClientInfo,
|
||||||
|
inbound: &TcpStream,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<(Client, TcpStream, BytesMut), ()> {
|
) -> Result<(Client, TcpStream, BytesMut), ()> {
|
||||||
time::timeout(
|
time::timeout(
|
||||||
SERVER_CONNECT_TIMEOUT,
|
SERVER_CONNECT_TIMEOUT,
|
||||||
connect_to_server_no_timeout(client_info, config),
|
connect_to_server_no_timeout(client_info, inbound, config),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
@@ -630,6 +581,7 @@ async fn connect_to_server(
|
|||||||
// TODO: clean this up
|
// TODO: clean this up
|
||||||
async fn connect_to_server_no_timeout(
|
async fn connect_to_server_no_timeout(
|
||||||
client_info: ClientInfo,
|
client_info: ClientInfo,
|
||||||
|
inbound: &TcpStream,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<(Client, TcpStream, BytesMut), ()> {
|
) -> Result<(Client, TcpStream, BytesMut), ()> {
|
||||||
// Open connection
|
// Open connection
|
||||||
@@ -638,44 +590,53 @@ async fn connect_to_server_no_timeout(
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| ())?;
|
.map_err(|_| ())?;
|
||||||
|
|
||||||
let (mut reader, mut writer) = outbound.split();
|
// 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(|_| ())?;
|
||||||
|
}
|
||||||
|
|
||||||
let tmp_client = Client::default();
|
// Construct temporary server client
|
||||||
|
let tmp_client = match outbound.local_addr() {
|
||||||
|
Ok(addr) => Client::new(addr),
|
||||||
|
Err(_) => Client::dummy(),
|
||||||
|
};
|
||||||
tmp_client.set_state(ClientState::Login);
|
tmp_client.set_state(ClientState::Login);
|
||||||
|
|
||||||
|
let (mut reader, mut writer) = outbound.split();
|
||||||
|
|
||||||
// Handshake packet
|
// Handshake packet
|
||||||
let packet = Handshake {
|
packet::write_packet(
|
||||||
protocol_version: client_info.protocol_version.unwrap(),
|
Handshake {
|
||||||
server_addr: config.server.address.ip().to_string(),
|
protocol_version: client_info.protocol_version.unwrap(),
|
||||||
server_port: config.server.address.port(),
|
server_addr: config.server.address.ip().to_string(),
|
||||||
next_state: ClientState::Login.to_id(),
|
server_port: config.server.address.port(),
|
||||||
};
|
next_state: ClientState::Login.to_id(),
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
&tmp_client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
&mut writer,
|
||||||
|
)
|
||||||
let request =
|
.await?;
|
||||||
RawPacket::new(proto::packets::handshake::SERVER_HANDSHAKE, data).encode(&tmp_client)?;
|
|
||||||
writer.write_all(&request).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
// Request login start
|
// Request login start
|
||||||
let packet = LoginStart {
|
packet::write_packet(
|
||||||
name: client_info.username.ok_or(())?,
|
LoginStart {
|
||||||
};
|
name: client_info.username.ok_or(())?,
|
||||||
|
},
|
||||||
let mut data = Vec::new();
|
&tmp_client,
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
&mut writer,
|
||||||
|
)
|
||||||
let request =
|
.await?;
|
||||||
RawPacket::new(proto::packets::login::SERVER_LOGIN_START, data).encode(&tmp_client)?;
|
|
||||||
writer.write_all(&request).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
// Incoming buffer
|
// Incoming buffer
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, _raw) = match proto::read_packet(&tmp_client, &mut buf, &mut reader).await {
|
let (packet, _raw) = match packet::read_packet(&tmp_client, &mut buf, &mut reader).await {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -688,8 +649,7 @@ async fn connect_to_server_no_timeout(
|
|||||||
let client_state = tmp_client.state();
|
let client_state = tmp_client.state();
|
||||||
|
|
||||||
// Catch set compression
|
// Catch set compression
|
||||||
if client_state == ClientState::Login
|
if client_state == ClientState::Login && packet.id == packets::login::CLIENT_SET_COMPRESSION
|
||||||
&& packet.id == proto::packets::login::CLIENT_SET_COMPRESSION
|
|
||||||
{
|
{
|
||||||
// Decode compression packet
|
// Decode compression packet
|
||||||
let set_compression =
|
let set_compression =
|
||||||
@@ -713,9 +673,7 @@ async fn connect_to_server_no_timeout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hijack login success
|
// Hijack login success
|
||||||
if client_state == ClientState::Login
|
if client_state == ClientState::Login && packet.id == packets::login::CLIENT_LOGIN_SUCCESS {
|
||||||
&& packet.id == proto::packets::login::CLIENT_LOGIN_SUCCESS
|
|
||||||
{
|
|
||||||
trace!(target: "lazymc::lobby", "Received login success from server connection, change to play mode");
|
trace!(target: "lazymc::lobby", "Received login success from server connection, change to play mode");
|
||||||
|
|
||||||
// TODO: parse this packet to ensure it's fine
|
// TODO: parse this packet to ensure it's fine
|
||||||
@@ -743,11 +701,7 @@ async fn connect_to_server_no_timeout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gracefully close connection
|
// Gracefully close connection
|
||||||
match writer.shutdown().await {
|
net::close_tcp_stream(outbound).await.map_err(|_| ())?;
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
|
||||||
Err(_) => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
@@ -780,11 +734,11 @@ async fn wait_for_server_join_game_no_timeout(
|
|||||||
outbound: &mut TcpStream,
|
outbound: &mut TcpStream,
|
||||||
buf: &mut BytesMut,
|
buf: &mut BytesMut,
|
||||||
) -> Result<JoinGame, ()> {
|
) -> Result<JoinGame, ()> {
|
||||||
let (mut reader, mut writer) = outbound.split();
|
let (mut reader, mut _writer) = outbound.split();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, _raw) = match proto::read_packet(client, buf, &mut reader).await {
|
let (packet, _raw) = match packet::read_packet(client, buf, &mut reader).await {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -794,7 +748,7 @@ async fn wait_for_server_join_game_no_timeout(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Catch join game
|
// Catch join game
|
||||||
if packet.id == proto::packets::play::CLIENT_JOIN_GAME {
|
if packet.id == packets::play::CLIENT_JOIN_GAME {
|
||||||
let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|err| {
|
let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|err| {
|
||||||
dbg!(err);
|
dbg!(err);
|
||||||
})?;
|
})?;
|
||||||
@@ -808,11 +762,7 @@ async fn wait_for_server_join_game_no_timeout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gracefully close connection
|
// Gracefully close connection
|
||||||
match writer.shutdown().await {
|
net::close_tcp_stream_ref(outbound).await.map_err(|_| ())?;
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
|
||||||
Err(_) => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
@@ -10,10 +10,12 @@ extern crate log;
|
|||||||
pub(crate) mod action;
|
pub(crate) mod action;
|
||||||
pub(crate) mod cli;
|
pub(crate) mod cli;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
|
pub(crate) mod join;
|
||||||
#[cfg(feature = "lobby")]
|
#[cfg(feature = "lobby")]
|
||||||
pub(crate) mod lobby;
|
pub(crate) mod lobby;
|
||||||
pub(crate) mod mc;
|
pub(crate) mod mc;
|
||||||
pub(crate) mod monitor;
|
pub(crate) mod monitor;
|
||||||
|
pub(crate) mod net;
|
||||||
pub(crate) mod os;
|
pub(crate) mod os;
|
||||||
pub(crate) mod proto;
|
pub(crate) mod proto;
|
||||||
pub(crate) mod proxy;
|
pub(crate) mod proxy;
|
||||||
|
99
src/mc/ban.rs
Normal file
99
src/mc/ban.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// File name.
|
||||||
|
pub const FILE: &str = "banned-ips.json";
|
||||||
|
|
||||||
|
/// The forever expiry literal.
|
||||||
|
const EXPIRY_FOREVER: &str = "forever";
|
||||||
|
|
||||||
|
/// List of banned IPs.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct BannedIps {
|
||||||
|
/// List of banned IPs.
|
||||||
|
ips: HashMap<IpAddr, BannedIp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BannedIps {
|
||||||
|
/// Get ban entry if IP if it exists.
|
||||||
|
///
|
||||||
|
/// This uses the latest known `banned-ips.json` contents if known.
|
||||||
|
/// If this feature is disabled, this will always return false.
|
||||||
|
pub fn get(&self, ip: &IpAddr) -> Option<BannedIp> {
|
||||||
|
self.ips.get(ip).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the given IP is banned.
|
||||||
|
///
|
||||||
|
/// This uses the latest known `banned-ips.json` contents if known.
|
||||||
|
/// If this feature is disabled, this will always return false.
|
||||||
|
pub fn is_banned(&self, ip: &IpAddr) -> bool {
|
||||||
|
self.ips.get(ip).map(|ip| ip.is_banned()).unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A banned IP entry.
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct BannedIp {
|
||||||
|
/// Banned IP.
|
||||||
|
pub ip: IpAddr,
|
||||||
|
|
||||||
|
/// Ban creation time.
|
||||||
|
pub created: Option<String>,
|
||||||
|
|
||||||
|
/// Ban source.
|
||||||
|
pub source: Option<String>,
|
||||||
|
|
||||||
|
/// Ban expiry time.
|
||||||
|
pub expires: Option<String>,
|
||||||
|
|
||||||
|
/// Ban reason.
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BannedIp {
|
||||||
|
/// Check if this entry is currently banned.
|
||||||
|
pub fn is_banned(&self) -> bool {
|
||||||
|
// Get expiry time
|
||||||
|
let expires = match &self.expires {
|
||||||
|
Some(expires) => expires,
|
||||||
|
None => return true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If expiry is forever, the user is banned
|
||||||
|
if expires.trim().to_lowercase() == EXPIRY_FOREVER {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse expiry time, check if it has passed
|
||||||
|
let expiry = match DateTime::parse_from_str(expires, "%Y-%m-%d %H:%M:%S %z") {
|
||||||
|
Ok(expiry) => expiry,
|
||||||
|
Err(err) => {
|
||||||
|
error!(target: "lazymc", "Failed to parse ban expiry '{}', assuming still banned: {}", expires, err);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expiry > Utc::now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load banned IPs from file.
|
||||||
|
pub fn load(path: &Path) -> Result<BannedIps, Box<dyn Error>> {
|
||||||
|
// Load file contents
|
||||||
|
let contents = fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
// Parse contents
|
||||||
|
let ips: Vec<BannedIp> = serde_json::from_str(&contents)?;
|
||||||
|
debug!(target: "lazymc", "Loaded {} banned IPs", ips.len());
|
||||||
|
|
||||||
|
// Transform into map
|
||||||
|
let ips = ips.into_iter().map(|ip| (ip.ip, ip)).collect();
|
||||||
|
Ok(BannedIps { ips })
|
||||||
|
}
|
26
src/mc/favicon.rs
Normal file
26
src/mc/favicon.rs
Normal 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)
|
||||||
|
}
|
@@ -1,6 +1,10 @@
|
|||||||
|
pub mod ban;
|
||||||
|
pub mod favicon;
|
||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
pub mod rcon;
|
pub mod rcon;
|
||||||
pub mod server_properties;
|
pub mod server_properties;
|
||||||
|
#[cfg(feature = "lobby")]
|
||||||
|
pub mod uuid;
|
||||||
|
|
||||||
/// Minecraft ticks per second.
|
/// Minecraft ticks per second.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use async_std::net::TcpStream;
|
||||||
|
use async_std::prelude::*;
|
||||||
use rust_rcon::{Connection, Error as RconError};
|
use rust_rcon::{Connection, Error as RconError};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::proxy;
|
||||||
|
|
||||||
/// Minecraft RCON quirk.
|
/// Minecraft RCON quirk.
|
||||||
///
|
///
|
||||||
/// Wait this time between RCON operations.
|
/// Wait this time between RCON operations.
|
||||||
@@ -17,16 +22,39 @@ pub struct Rcon {
|
|||||||
|
|
||||||
impl Rcon {
|
impl Rcon {
|
||||||
/// Connect to a host.
|
/// 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
|
// Start connection
|
||||||
let con = Connection::builder()
|
let con = Connection::builder()
|
||||||
.enable_minecraft_quirks(true)
|
.enable_minecraft_quirks(true)
|
||||||
.connect(addr, pass)
|
.connect_stream(stream, pass)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self { con })
|
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.
|
/// Send command over RCON.
|
||||||
pub async fn cmd(&mut self, cmd: &str) -> Result<String, RconError> {
|
pub async fn cmd(&mut self, cmd: &str) -> Result<String, RconError> {
|
||||||
// Minecraft quirk
|
// Minecraft quirk
|
||||||
|
@@ -11,39 +11,39 @@ const EOL: &str = "\r\n";
|
|||||||
/// Try to rewrite changes in server.properties file in dir.
|
/// Try to rewrite changes in server.properties file in dir.
|
||||||
///
|
///
|
||||||
/// Prints an error and stops on failure.
|
/// 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() {
|
if changes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
if !dir.is_dir() {
|
if !dir.as_ref().is_dir() {
|
||||||
warn!(target: "lazymc",
|
warn!(target: "lazymc",
|
||||||
"Not rewriting {} file, configured server directory doesn't exist: {}",
|
"Not rewriting {} file, configured server directory doesn't exist: {}",
|
||||||
FILE,
|
FILE,
|
||||||
dir.to_str().unwrap_or("?")
|
dir.as_ref().to_str().unwrap_or("?")
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite file
|
// Rewrite file
|
||||||
rewrite_file(&dir.join(FILE), changes)
|
rewrite_file(dir.as_ref().join(FILE), changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to rewrite changes in server.properties file.
|
/// Try to rewrite changes in server.properties file.
|
||||||
///
|
///
|
||||||
/// Prints an error and stops on failure.
|
/// 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() {
|
if changes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// File must exist
|
// File must exist
|
||||||
if !file.is_file() {
|
if !file.as_ref().is_file() {
|
||||||
warn!(target: "lazymc",
|
warn!(target: "lazymc",
|
||||||
"Not writing {} file, not found at: {}",
|
"Not writing {} file, not found at: {}",
|
||||||
FILE,
|
FILE,
|
||||||
file.to_str().unwrap_or("?"),
|
file.as_ref().to_str().unwrap_or("?"),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
33
src/mc/uuid.rs
Normal file
33
src/mc/uuid.rs
Normal 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)
|
||||||
|
}
|
@@ -1,5 +1,3 @@
|
|||||||
// TODO: remove all unwraps/expects here!
|
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -7,16 +5,19 @@ use std::time::Duration;
|
|||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use minecraft_protocol::data::server_status::ServerStatus;
|
use minecraft_protocol::data::server_status::ServerStatus;
|
||||||
use minecraft_protocol::decoder::Decoder;
|
use minecraft_protocol::decoder::Decoder;
|
||||||
use minecraft_protocol::encoder::Encoder;
|
|
||||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||||
use minecraft_protocol::version::v1_14_4::status::{PingRequest, PingResponse, StatusResponse};
|
use minecraft_protocol::version::v1_14_4::status::{
|
||||||
|
PingRequest, PingResponse, StatusRequest, StatusResponse,
|
||||||
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::proto::{self, Client, ClientState, RawPacket};
|
use crate::proto::client::{Client, ClientState};
|
||||||
|
use crate::proto::{packet, packets};
|
||||||
|
use crate::proxy;
|
||||||
use crate::server::{Server, State};
|
use crate::server::{Server, State};
|
||||||
|
|
||||||
/// Monitor ping inverval in seconds.
|
/// Monitor ping inverval in seconds.
|
||||||
@@ -26,7 +27,7 @@ const MONITOR_POLL_INTERVAL: Duration = Duration::from_secs(2);
|
|||||||
const STATUS_TIMEOUT: u64 = 20;
|
const STATUS_TIMEOUT: u64 = 20;
|
||||||
|
|
||||||
/// Ping request timeout in seconds.
|
/// Ping request timeout in seconds.
|
||||||
const PING_TIMEOUT: u64 = 20;
|
const PING_TIMEOUT: u64 = 10;
|
||||||
|
|
||||||
/// Monitor server.
|
/// Monitor server.
|
||||||
pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
|
pub async fn monitor_server(config: Arc<Config>, server: Arc<Server>) {
|
||||||
@@ -86,6 +87,7 @@ pub async fn poll_server(
|
|||||||
|
|
||||||
// Try ping fallback if server is currently started
|
// Try ping fallback if server is currently started
|
||||||
if server.state() == State::Started {
|
if server.state() == State::Started {
|
||||||
|
debug!(target: "lazymc::monitor", "Failed to get status from started server, trying ping...");
|
||||||
do_ping(config, addr).await?;
|
do_ping(config, addr).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +98,17 @@ pub async fn poll_server(
|
|||||||
async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus, ()> {
|
async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus, ()> {
|
||||||
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
|
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
|
// Dummy client
|
||||||
let client = Client::default();
|
let client = Client::dummy();
|
||||||
|
|
||||||
send_handshake(&client, &mut stream, config, addr).await?;
|
send_handshake(&client, &mut stream, config, addr).await?;
|
||||||
request_status(&client, &mut stream).await?;
|
request_status(&client, &mut stream).await?;
|
||||||
@@ -108,8 +119,17 @@ async fn fetch_status(config: &Config, addr: SocketAddr) -> Result<ServerStatus,
|
|||||||
async fn do_ping(config: &Config, addr: SocketAddr) -> Result<(), ()> {
|
async fn do_ping(config: &Config, addr: SocketAddr) -> Result<(), ()> {
|
||||||
let mut stream = TcpStream::connect(addr).await.map_err(|_| ())?;
|
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
|
// Dummy client
|
||||||
let client = Client::default();
|
let client = Client::dummy();
|
||||||
|
|
||||||
send_handshake(&client, &mut stream, config, addr).await?;
|
send_handshake(&client, &mut stream, config, addr).await?;
|
||||||
let token = send_ping(&client, &mut stream).await?;
|
let token = send_ping(&client, &mut stream).await?;
|
||||||
@@ -123,45 +143,28 @@ async fn send_handshake(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let handshake = Handshake {
|
packet::write_packet(
|
||||||
protocol_version: config.public.protocol as i32,
|
Handshake {
|
||||||
server_addr: addr.ip().to_string(),
|
protocol_version: config.public.protocol as i32,
|
||||||
server_port: addr.port(),
|
server_addr: addr.ip().to_string(),
|
||||||
next_state: ClientState::Status.to_id(),
|
server_port: addr.port(),
|
||||||
};
|
next_state: ClientState::Status.to_id(),
|
||||||
|
},
|
||||||
let mut packet = Vec::new();
|
client,
|
||||||
handshake.encode(&mut packet).map_err(|_| ())?;
|
&mut stream.split().1,
|
||||||
|
)
|
||||||
let raw = RawPacket::new(proto::packets::handshake::SERVER_HANDSHAKE, packet)
|
.await
|
||||||
.encode(client)
|
|
||||||
.map_err(|_| ())?;
|
|
||||||
stream.write_all(&raw).await.map_err(|_| ())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send status request.
|
/// Send status request.
|
||||||
async fn request_status(client: &Client, stream: &mut TcpStream) -> Result<(), ()> {
|
async fn request_status(client: &Client, stream: &mut TcpStream) -> Result<(), ()> {
|
||||||
let raw = RawPacket::new(proto::packets::status::SERVER_STATUS, vec![])
|
packet::write_packet(StatusRequest {}, client, &mut stream.split().1).await
|
||||||
.encode(client)
|
|
||||||
.map_err(|_| ())?;
|
|
||||||
stream.write_all(&raw).await.map_err(|_| ())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send status request.
|
/// Send status request.
|
||||||
async fn send_ping(client: &Client, stream: &mut TcpStream) -> Result<u64, ()> {
|
async fn send_ping(client: &Client, stream: &mut TcpStream) -> Result<u64, ()> {
|
||||||
let token = rand::thread_rng().gen();
|
let token = rand::thread_rng().gen();
|
||||||
let ping = PingRequest { time: token };
|
packet::write_packet(PingRequest { time: token }, client, &mut stream.split().1).await?;
|
||||||
|
|
||||||
let mut packet = Vec::new();
|
|
||||||
ping.encode(&mut packet).map_err(|_| ())?;
|
|
||||||
|
|
||||||
let raw = RawPacket::new(proto::packets::status::SERVER_PING, packet)
|
|
||||||
.encode(client)
|
|
||||||
.map_err(|_| ())?;
|
|
||||||
stream.write_all(&raw).await.map_err(|_| ())?;
|
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +176,14 @@ async fn wait_for_status(client: &Client, stream: &mut TcpStream) -> Result<Serv
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, _raw) = match proto::read_packet(client, &mut buf, &mut reader).await {
|
let (packet, _raw) = match packet::read_packet(client, &mut buf, &mut reader).await {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Catch status response
|
// Catch status response
|
||||||
if packet.id == proto::packets::status::CLIENT_STATUS {
|
if packet.id == packets::status::CLIENT_STATUS {
|
||||||
let status = StatusResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
let status = StatusResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||||
return Ok(status.server_status);
|
return Ok(status.server_status);
|
||||||
}
|
}
|
||||||
@@ -209,14 +212,14 @@ async fn wait_for_ping(client: &Client, stream: &mut TcpStream, token: u64) -> R
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, _raw) = match proto::read_packet(client, &mut buf, &mut reader).await {
|
let (packet, _raw) = match packet::read_packet(client, &mut buf, &mut reader).await {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Catch ping response
|
// Catch ping response
|
||||||
if packet.id == proto::packets::status::CLIENT_PING {
|
if packet.id == packets::status::CLIENT_PING {
|
||||||
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
let ping = PingResponse::decode(&mut packet.data.as_slice()).map_err(|_| ())?;
|
||||||
|
|
||||||
// Ping token must match
|
// Ping token must match
|
||||||
|
22
src/net.rs
Normal file
22
src/net.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
/// Gracefully close given TCP stream.
|
||||||
|
///
|
||||||
|
/// Intended as helper to make code less messy. This also succeeds if already closed.
|
||||||
|
pub async fn close_tcp_stream(mut stream: TcpStream) -> Result<(), Box<dyn Error>> {
|
||||||
|
close_tcp_stream_ref(&mut stream).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gracefully close given TCP stream.
|
||||||
|
///
|
||||||
|
/// Intended as helper to make code less messy. This also succeeds if already closed.
|
||||||
|
pub async fn close_tcp_stream_ref(stream: &mut TcpStream) -> Result<(), Box<dyn Error>> {
|
||||||
|
match stream.shutdown().await {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::NotConnected => Ok(()),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
36
src/proto/action.rs
Normal file
36
src/proto/action.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use minecraft_protocol::data::chat::{Message, Payload};
|
||||||
|
use minecraft_protocol::version::v1_14_4::game::GameDisconnect;
|
||||||
|
use minecraft_protocol::version::v1_14_4::login::LoginDisconnect;
|
||||||
|
use tokio::net::tcp::WriteHalf;
|
||||||
|
|
||||||
|
use crate::proto::client::{Client, ClientState};
|
||||||
|
use crate::proto::packet;
|
||||||
|
|
||||||
|
/// Kick client with a message.
|
||||||
|
///
|
||||||
|
/// Should close connection afterwards.
|
||||||
|
pub async fn kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||||
|
match client.state() {
|
||||||
|
ClientState::Login => {
|
||||||
|
packet::write_packet(
|
||||||
|
LoginDisconnect {
|
||||||
|
reason: Message::new(Payload::text(msg)),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
ClientState::Play => {
|
||||||
|
packet::write_packet(
|
||||||
|
GameDisconnect {
|
||||||
|
reason: Message::new(Payload::text(msg)),
|
||||||
|
},
|
||||||
|
client,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
127
src/proto/client.rs
Normal file
127
src/proto/client.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
/// Client state.
|
||||||
|
///
|
||||||
|
/// Note: this does not keep track of encryption states.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
/// Client peer address.
|
||||||
|
pub peer: SocketAddr,
|
||||||
|
|
||||||
|
/// Current client state.
|
||||||
|
pub state: Mutex<ClientState>,
|
||||||
|
|
||||||
|
/// Compression state.
|
||||||
|
///
|
||||||
|
/// 0 or positive if enabled, negative if disabled.
|
||||||
|
pub compression: AtomicI32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Construct new client with given peer address.
|
||||||
|
pub fn new(peer: SocketAddr) -> Self {
|
||||||
|
Self {
|
||||||
|
peer,
|
||||||
|
state: Default::default(),
|
||||||
|
compression: AtomicI32::new(-1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct dummy client.
|
||||||
|
pub fn dummy() -> Self {
|
||||||
|
Self::new("0.0.0.0:0".parse().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get client state.
|
||||||
|
pub fn state(&self) -> ClientState {
|
||||||
|
*self.state.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set client state.
|
||||||
|
pub fn set_state(&self, state: ClientState) {
|
||||||
|
*self.state.lock().unwrap() = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get compression threshold.
|
||||||
|
pub fn compressed(&self) -> i32 {
|
||||||
|
self.compression.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether compression is used.
|
||||||
|
pub fn is_compressed(&self) -> bool {
|
||||||
|
self.compressed() >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set compression value.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_compression(&self, threshold: i32) {
|
||||||
|
trace!(target: "lazymc", "Client now uses compression threshold of {}", threshold);
|
||||||
|
self.compression.store(threshold, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Protocol state a client may be in.
|
||||||
|
///
|
||||||
|
/// Note: this does not include the `play` state, because this is never used anymore when a client
|
||||||
|
/// reaches this state.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum ClientState {
|
||||||
|
/// Initial client state.
|
||||||
|
Handshake,
|
||||||
|
|
||||||
|
/// State to query server status.
|
||||||
|
Status,
|
||||||
|
|
||||||
|
/// State to login to server.
|
||||||
|
Login,
|
||||||
|
|
||||||
|
/// State to play on the server.
|
||||||
|
#[allow(unused)]
|
||||||
|
Play,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientState {
|
||||||
|
/// From state ID.
|
||||||
|
pub fn from_id(id: i32) -> Option<Self> {
|
||||||
|
match id {
|
||||||
|
0 => Some(Self::Handshake),
|
||||||
|
1 => Some(Self::Status),
|
||||||
|
2 => Some(Self::Login),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get state ID.
|
||||||
|
pub fn to_id(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::Handshake => 0,
|
||||||
|
Self::Status => 1,
|
||||||
|
Self::Login => 2,
|
||||||
|
Self::Play => -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Handshake
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Client info, useful during connection handling.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ClientInfo {
|
||||||
|
/// Client protocol version.
|
||||||
|
pub protocol_version: Option<i32>,
|
||||||
|
|
||||||
|
/// Client username.
|
||||||
|
pub username: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientInfo {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
27
src/proto/mod.rs
Normal file
27
src/proto/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
pub mod action;
|
||||||
|
pub mod client;
|
||||||
|
pub mod packet;
|
||||||
|
pub mod packets;
|
||||||
|
|
||||||
|
/// Default minecraft protocol version name.
|
||||||
|
///
|
||||||
|
/// Just something to default to when real server version isn't known or when no hint is specified
|
||||||
|
/// in the configuration.
|
||||||
|
///
|
||||||
|
/// Should be kept up-to-date with latest supported Minecraft version by lazymc.
|
||||||
|
pub const PROTO_DEFAULT_VERSION: &str = "1.17.1";
|
||||||
|
|
||||||
|
/// Default minecraft protocol version.
|
||||||
|
///
|
||||||
|
/// Just something to default to when real server version isn't known or when no hint is specified
|
||||||
|
/// in the configuration.
|
||||||
|
///
|
||||||
|
/// Should be kept up-to-date with latest supported Minecraft version by lazymc.
|
||||||
|
pub const PROTO_DEFAULT_PROTOCOL: u32 = 756;
|
||||||
|
|
||||||
|
/// Compression threshold to use.
|
||||||
|
// TODO: read this from server.properties instead
|
||||||
|
pub const COMPRESSION_THRESHOLD: i32 = 256;
|
||||||
|
|
||||||
|
/// Default buffer size when reading packets.
|
||||||
|
pub(super) const BUF_SIZE: usize = 8 * 1024;
|
@@ -1,204 +1,25 @@
|
|||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use flate2::read::ZlibDecoder;
|
use flate2::read::ZlibDecoder;
|
||||||
use flate2::write::ZlibEncoder;
|
use flate2::write::ZlibEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
|
use minecraft_protocol::encoder::Encoder;
|
||||||
|
use minecraft_protocol::version::PacketId;
|
||||||
use tokio::io;
|
use tokio::io;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::tcp::ReadHalf;
|
use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
|
|
||||||
|
use crate::proto::client::Client;
|
||||||
|
use crate::proto::BUF_SIZE;
|
||||||
use crate::types;
|
use crate::types;
|
||||||
|
|
||||||
/// Default minecraft protocol version name.
|
|
||||||
///
|
|
||||||
/// Just something to default to when real server version isn't known or when no hint is specified
|
|
||||||
/// in the configuration.
|
|
||||||
///
|
|
||||||
/// Should be kept up-to-date with latest supported Minecraft version by lazymc.
|
|
||||||
pub const PROTO_DEFAULT_VERSION: &str = "1.17.1";
|
|
||||||
|
|
||||||
/// Default minecraft protocol version.
|
|
||||||
///
|
|
||||||
/// Just something to default to when real server version isn't known or when no hint is specified
|
|
||||||
/// in the configuration.
|
|
||||||
///
|
|
||||||
/// Should be kept up-to-date with latest supported Minecraft version by lazymc.
|
|
||||||
pub const PROTO_DEFAULT_PROTOCOL: u32 = 756;
|
|
||||||
|
|
||||||
/// Compression threshold to use.
|
|
||||||
// TODO: read this from server.properties instead
|
|
||||||
pub const COMPRESSION_THRESHOLD: i32 = 256;
|
|
||||||
|
|
||||||
/// Default buffer size when reading packets.
|
|
||||||
const BUF_SIZE: usize = 8 * 1024;
|
|
||||||
|
|
||||||
/// Minecraft protocol packet IDs.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub mod packets {
|
|
||||||
pub mod handshake {
|
|
||||||
pub const SERVER_HANDSHAKE: i32 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod status {
|
|
||||||
pub const CLIENT_STATUS: i32 = 0;
|
|
||||||
pub const CLIENT_PING: i32 = 1;
|
|
||||||
pub const SERVER_STATUS: i32 = 0;
|
|
||||||
pub const SERVER_PING: i32 = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod login {
|
|
||||||
pub const CLIENT_DISCONNECT: i32 = 0x00;
|
|
||||||
pub const CLIENT_LOGIN_SUCCESS: i32 = 0x02;
|
|
||||||
pub const CLIENT_SET_COMPRESSION: i32 = 0x03;
|
|
||||||
pub const SERVER_LOGIN_START: i32 = 0x00;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod play {
|
|
||||||
pub const CLIENT_CHAT_MSG: i32 = 0x0F;
|
|
||||||
pub const CLIENT_PLUGIN_MESSAGE: i32 = 0x18;
|
|
||||||
pub const CLIENT_NAMED_SOUND_EFFECT: i32 = 0x19;
|
|
||||||
pub const CLIENT_DISCONNECT: i32 = 0x1A;
|
|
||||||
pub const CLIENT_KEEP_ALIVE: i32 = 0x21;
|
|
||||||
pub const CLIENT_JOIN_GAME: i32 = 0x26;
|
|
||||||
pub const CLIENT_PLAYER_POS_LOOK: i32 = 0x38;
|
|
||||||
pub const CLIENT_RESPAWN: i32 = 0x3D;
|
|
||||||
pub const CLIENT_SPAWN_POS: i32 = 0x4B;
|
|
||||||
pub const CLIENT_SET_TITLE_SUBTITLE: i32 = 0x57;
|
|
||||||
pub const CLIENT_TIME_UPDATE: i32 = 0x58;
|
|
||||||
pub const CLIENT_SET_TITLE_TEXT: i32 = 0x59;
|
|
||||||
pub const CLIENT_SET_TITLE_TIMES: i32 = 0x5A;
|
|
||||||
pub const SERVER_CLIENT_SETTINGS: i32 = 0x05;
|
|
||||||
pub const SERVER_PLUGIN_MESSAGE: i32 = 0x0A;
|
|
||||||
pub const SERVER_PLAYER_POS: i32 = 0x11;
|
|
||||||
pub const SERVER_PLAYER_POS_ROT: i32 = 0x12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Client state.
|
|
||||||
///
|
|
||||||
/// Note: this does not keep track of encryption states.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
/// Current client state.
|
|
||||||
pub state: Mutex<ClientState>,
|
|
||||||
|
|
||||||
/// Compression state.
|
|
||||||
///
|
|
||||||
/// 0 or positive if enabled, negative if disabled.
|
|
||||||
pub compression: AtomicI32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
/// Get client state.
|
|
||||||
pub fn state(&self) -> ClientState {
|
|
||||||
*self.state.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set client state.
|
|
||||||
pub fn set_state(&self, state: ClientState) {
|
|
||||||
*self.state.lock().unwrap() = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get compression threshold.
|
|
||||||
pub fn compressed(&self) -> i32 {
|
|
||||||
self.compression.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether compression is used.
|
|
||||||
pub fn is_compressed(&self) -> bool {
|
|
||||||
self.compressed() >= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set compression value.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn set_compression(&self, threshold: i32) {
|
|
||||||
trace!(target: "lazymc", "Client now uses compression threshold of {}", threshold);
|
|
||||||
self.compression.store(threshold, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Client {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
state: Default::default(),
|
|
||||||
compression: AtomicI32::new(-1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Protocol state a client may be in.
|
|
||||||
///
|
|
||||||
/// Note: this does not include the `play` state, because this is never used anymore when a client
|
|
||||||
/// reaches this state.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum ClientState {
|
|
||||||
/// Initial client state.
|
|
||||||
Handshake,
|
|
||||||
|
|
||||||
/// State to query server status.
|
|
||||||
Status,
|
|
||||||
|
|
||||||
/// State to login to server.
|
|
||||||
Login,
|
|
||||||
|
|
||||||
/// State to play on the server.
|
|
||||||
#[allow(unused)]
|
|
||||||
Play,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientState {
|
|
||||||
/// From state ID.
|
|
||||||
pub fn from_id(id: i32) -> Option<Self> {
|
|
||||||
match id {
|
|
||||||
0 => Some(Self::Handshake),
|
|
||||||
1 => Some(Self::Status),
|
|
||||||
2 => Some(Self::Login),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get state ID.
|
|
||||||
pub fn to_id(self) -> i32 {
|
|
||||||
match self {
|
|
||||||
Self::Handshake => 0,
|
|
||||||
Self::Status => 1,
|
|
||||||
Self::Login => 2,
|
|
||||||
Self::Play => -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ClientState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Handshake
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Client info, useful during connection handling.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ClientInfo {
|
|
||||||
/// Client protocol version.
|
|
||||||
pub protocol_version: Option<i32>,
|
|
||||||
|
|
||||||
/// Client username.
|
|
||||||
pub username: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientInfo {
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raw Minecraft packet.
|
/// Raw Minecraft packet.
|
||||||
///
|
///
|
||||||
/// Having a packet ID and a raw data byte array.
|
/// Having a packet ID and a raw data byte array.
|
||||||
pub struct RawPacket {
|
pub struct RawPacket {
|
||||||
/// Packet ID.
|
/// Packet ID.
|
||||||
pub id: i32,
|
pub id: u8,
|
||||||
|
|
||||||
/// Packet data.
|
/// Packet data.
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
@@ -206,7 +27,7 @@ pub struct RawPacket {
|
|||||||
|
|
||||||
impl RawPacket {
|
impl RawPacket {
|
||||||
/// Construct new raw packet.
|
/// Construct new raw packet.
|
||||||
pub fn new(id: i32, data: Vec<u8>) -> Self {
|
pub fn new(id: u8, data: Vec<u8>) -> Self {
|
||||||
Self { id, data }
|
Self { id, data }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +37,7 @@ impl RawPacket {
|
|||||||
let (read, packet_id) = types::read_var_int(buf)?;
|
let (read, packet_id) = types::read_var_int(buf)?;
|
||||||
buf = &buf[read..];
|
buf = &buf[read..];
|
||||||
|
|
||||||
Ok(Self::new(packet_id, buf.to_vec()))
|
Ok(Self::new(packet_id as u8, buf.to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode packet from raw buffer.
|
/// Decode packet from raw buffer.
|
||||||
@@ -276,7 +97,7 @@ impl RawPacket {
|
|||||||
/// Encode compressed packet to raw buffer.
|
/// Encode compressed packet to raw buffer.
|
||||||
fn encode_compressed(&self, threshold: i32) -> Result<Vec<u8>, ()> {
|
fn encode_compressed(&self, threshold: i32) -> Result<Vec<u8>, ()> {
|
||||||
// Packet payload: packet ID and data buffer
|
// Packet payload: packet ID and data buffer
|
||||||
let mut payload = types::encode_var_int(self.id)?;
|
let mut payload = types::encode_var_int(self.id as i32)?;
|
||||||
payload.extend_from_slice(&self.data);
|
payload.extend_from_slice(&self.data);
|
||||||
|
|
||||||
// Determine whether to compress, encode data length bytes
|
// Determine whether to compress, encode data length bytes
|
||||||
@@ -307,7 +128,7 @@ impl RawPacket {
|
|||||||
|
|
||||||
/// Encode uncompressed packet to raw buffer.
|
/// Encode uncompressed packet to raw buffer.
|
||||||
fn encode_uncompressed(&self) -> Result<Vec<u8>, ()> {
|
fn encode_uncompressed(&self) -> Result<Vec<u8>, ()> {
|
||||||
let mut data = types::encode_var_int(self.id)?;
|
let mut data = types::encode_var_int(self.id as i32)?;
|
||||||
data.extend_from_slice(&self.data);
|
data.extend_from_slice(&self.data);
|
||||||
|
|
||||||
let len = data.len() as i32;
|
let len = data.len() as i32;
|
||||||
@@ -378,3 +199,18 @@ pub async fn read_packet(
|
|||||||
|
|
||||||
Ok(Some((packet, raw.to_vec())))
|
Ok(Some((packet, raw.to_vec())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write packet to stream writer.
|
||||||
|
pub async fn write_packet(
|
||||||
|
packet: impl PacketId + Encoder,
|
||||||
|
client: &Client,
|
||||||
|
writer: &mut WriteHalf<'_>,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
packet.encode(&mut data).map_err(|_| ())?;
|
||||||
|
|
||||||
|
let response = RawPacket::new(packet.packet_id(), data).encode(client)?;
|
||||||
|
writer.write_all(&response).await.map_err(|_| ())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
41
src/proto/packets.rs
Normal file
41
src/proto/packets.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
//! Minecraft protocol packet IDs.
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
pub mod handshake {
|
||||||
|
pub const SERVER_HANDSHAKE: u8 = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod status {
|
||||||
|
pub const CLIENT_STATUS: u8 = 0x0;
|
||||||
|
pub const CLIENT_PING: u8 = 0x01;
|
||||||
|
pub const SERVER_STATUS: u8 = 0x00;
|
||||||
|
pub const SERVER_PING: u8 = 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod login {
|
||||||
|
pub const CLIENT_DISCONNECT: u8 = 0x00;
|
||||||
|
pub const CLIENT_LOGIN_SUCCESS: u8 = 0x02;
|
||||||
|
pub const CLIENT_SET_COMPRESSION: u8 = 0x03;
|
||||||
|
pub const SERVER_LOGIN_START: u8 = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod play {
|
||||||
|
pub const CLIENT_CHAT_MSG: u8 = 0x0F;
|
||||||
|
pub const CLIENT_PLUGIN_MESSAGE: u8 = 0x18;
|
||||||
|
pub const CLIENT_NAMED_SOUND_EFFECT: u8 = 0x19;
|
||||||
|
pub const CLIENT_DISCONNECT: u8 = 0x1A;
|
||||||
|
pub const CLIENT_KEEP_ALIVE: u8 = 0x21;
|
||||||
|
pub const CLIENT_JOIN_GAME: u8 = 0x26;
|
||||||
|
pub const CLIENT_PLAYER_POS_LOOK: u8 = 0x38;
|
||||||
|
pub const CLIENT_RESPAWN: u8 = 0x3D;
|
||||||
|
pub const CLIENT_SPAWN_POS: u8 = 0x4B;
|
||||||
|
pub const CLIENT_SET_TITLE_SUBTITLE: u8 = 0x57;
|
||||||
|
pub const CLIENT_TIME_UPDATE: u8 = 0x58;
|
||||||
|
pub const CLIENT_SET_TITLE_TEXT: u8 = 0x59;
|
||||||
|
pub const CLIENT_SET_TITLE_TIMES: u8 = 0x5A;
|
||||||
|
pub const SERVER_CLIENT_SETTINGS: u8 = 0x05;
|
||||||
|
pub const SERVER_PLUGIN_MESSAGE: u8 = 0x0A;
|
||||||
|
pub const SERVER_PLAYER_POS: u8 = 0x11;
|
||||||
|
pub const SERVER_PLAYER_POS_ROT: u8 = 0x12;
|
||||||
|
}
|
105
src/proxy.rs
105
src/proxy.rs
@@ -1,13 +1,22 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use proxy_protocol::version2::{ProxyAddresses, ProxyCommand, ProxyTransportProtocol};
|
||||||
|
use proxy_protocol::EncodeError;
|
||||||
use tokio::io;
|
use tokio::io;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::net;
|
||||||
|
|
||||||
/// Proxy the inbound stream to a target address.
|
/// Proxy the inbound stream to a target address.
|
||||||
pub async fn proxy(inbound: TcpStream, addr_target: SocketAddr) -> Result<(), Box<dyn Error>> {
|
pub async fn proxy(
|
||||||
proxy_with_queue(inbound, addr_target, &[]).await
|
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.
|
/// Proxy the inbound stream to a target address.
|
||||||
@@ -15,12 +24,26 @@ pub async fn proxy(inbound: TcpStream, addr_target: SocketAddr) -> Result<(), Bo
|
|||||||
/// Send the queue to the target server before proxying.
|
/// Send the queue to the target server before proxying.
|
||||||
pub async fn proxy_with_queue(
|
pub async fn proxy_with_queue(
|
||||||
inbound: TcpStream,
|
inbound: TcpStream,
|
||||||
|
proxy_header: ProxyHeader,
|
||||||
addr_target: SocketAddr,
|
addr_target: SocketAddr,
|
||||||
queue: &[u8],
|
queue: &[u8],
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
// Set up connection to server
|
// Set up connection to server
|
||||||
// TODO: on connect fail, ping server and redirect to serve_status if offline
|
// 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
|
// Start proxy on both streams
|
||||||
proxy_inbound_outbound_with_queue(inbound, outbound, &[], queue).await
|
proxy_inbound_outbound_with_queue(inbound, outbound, &[], queue).await
|
||||||
@@ -64,5 +87,81 @@ pub async fn proxy_inbound_outbound_with_queue(
|
|||||||
|
|
||||||
tokio::try_join!(client_to_server, server_to_client)?;
|
tokio::try_join!(client_to_server, server_to_client)?;
|
||||||
|
|
||||||
|
// Gracefully close connection if not done already
|
||||||
|
net::close_tcp_stream(inbound).await?;
|
||||||
|
|
||||||
Ok(())
|
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)
|
||||||
|
}
|
||||||
|
137
src/server.rs
137
src/server.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -11,7 +12,8 @@ use tokio::sync::Semaphore;
|
|||||||
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
|
use tokio::sync::{Mutex, RwLock, RwLockReadGuard};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::{Config, Server as ConfigServer};
|
||||||
|
use crate::mc::ban::{BannedIp, BannedIps};
|
||||||
use crate::os;
|
use crate::os;
|
||||||
|
|
||||||
/// Server cooldown after the process quit.
|
/// Server cooldown after the process quit.
|
||||||
@@ -25,44 +27,9 @@ const SERVER_QUIT_COOLDOWN: Duration = Duration::from_millis(2500);
|
|||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
const RCON_COOLDOWN: Duration = Duration::from_secs(15);
|
const RCON_COOLDOWN: Duration = Duration::from_secs(15);
|
||||||
|
|
||||||
/// Server state.
|
/// Exit code when SIGTERM is received on Unix.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[cfg(unix)]
|
||||||
pub enum State {
|
const UNIX_EXIT_SIGTERM: i32 = 130;
|
||||||
/// Server is stopped.
|
|
||||||
Stopped,
|
|
||||||
|
|
||||||
/// Server is starting.
|
|
||||||
Starting,
|
|
||||||
|
|
||||||
/// Server is online and responding.
|
|
||||||
Started,
|
|
||||||
|
|
||||||
/// Server is stopping.
|
|
||||||
Stopping,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
/// From u8, panics if invalid.
|
|
||||||
pub fn from_u8(state: u8) -> Self {
|
|
||||||
match state {
|
|
||||||
0 => Self::Stopped,
|
|
||||||
1 => Self::Starting,
|
|
||||||
2 => Self::Started,
|
|
||||||
3 => Self::Stopping,
|
|
||||||
_ => panic!("invalid State u8"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To u8.
|
|
||||||
pub fn to_u8(self) -> u8 {
|
|
||||||
match self {
|
|
||||||
Self::Stopped => 0,
|
|
||||||
Self::Starting => 1,
|
|
||||||
Self::Started => 2,
|
|
||||||
Self::Stopping => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shared server state.
|
/// Shared server state.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -102,6 +69,9 @@ pub struct Server {
|
|||||||
/// Used as starting/stopping timeout.
|
/// Used as starting/stopping timeout.
|
||||||
kill_at: RwLock<Option<Instant>>,
|
kill_at: RwLock<Option<Instant>>,
|
||||||
|
|
||||||
|
/// List of banned IPs.
|
||||||
|
banned_ips: RwLock<BannedIps>,
|
||||||
|
|
||||||
/// Lock for exclusive RCON operations.
|
/// Lock for exclusive RCON operations.
|
||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
rcon_lock: Semaphore,
|
rcon_lock: Semaphore,
|
||||||
@@ -345,6 +315,37 @@ impl Server {
|
|||||||
.filter(|d| *d > 0)
|
.filter(|d| *d > 0)
|
||||||
.map(|d| Instant::now() + Duration::from_secs(d as u64));
|
.map(|d| Instant::now() + Duration::from_secs(d as u64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the given IP is banned.
|
||||||
|
///
|
||||||
|
/// This uses the latest known `banned-ips.json` contents if known.
|
||||||
|
/// If this feature is disabled, this will always return false.
|
||||||
|
pub async fn is_banned_ip(&self, ip: &IpAddr) -> bool {
|
||||||
|
self.banned_ips.read().await.is_banned(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get user ban entry.
|
||||||
|
pub async fn ban_entry(&self, ip: &IpAddr) -> Option<BannedIp> {
|
||||||
|
self.banned_ips.read().await.get(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the given IP is banned.
|
||||||
|
///
|
||||||
|
/// This uses the latest known `banned-ips.json` contents if known.
|
||||||
|
/// If this feature is disabled, this will always return false.
|
||||||
|
pub fn is_banned_ip_blocking(&self, ip: &IpAddr) -> bool {
|
||||||
|
futures::executor::block_on(async { self.is_banned_ip(ip).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the list of banned IPs.
|
||||||
|
pub async fn set_banned_ips(&self, ips: BannedIps) {
|
||||||
|
*self.banned_ips.write().await = ips;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the list of banned IPs.
|
||||||
|
pub fn set_banned_ips_blocking(&self, ips: BannedIps) {
|
||||||
|
futures::executor::block_on(async { self.set_banned_ips(ips).await })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Server {
|
impl Default for Server {
|
||||||
@@ -360,6 +361,7 @@ impl Default for Server {
|
|||||||
last_active: Default::default(),
|
last_active: Default::default(),
|
||||||
keep_online_until: Default::default(),
|
keep_online_until: Default::default(),
|
||||||
kill_at: Default::default(),
|
kill_at: Default::default(),
|
||||||
|
banned_ips: Default::default(),
|
||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
rcon_lock: Semaphore::new(1),
|
rcon_lock: Semaphore::new(1),
|
||||||
#[cfg(feature = "rcon")]
|
#[cfg(feature = "rcon")]
|
||||||
@@ -368,6 +370,45 @@ impl Default for Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server state.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum State {
|
||||||
|
/// Server is stopped.
|
||||||
|
Stopped,
|
||||||
|
|
||||||
|
/// Server is starting.
|
||||||
|
Starting,
|
||||||
|
|
||||||
|
/// Server is online and responding.
|
||||||
|
Started,
|
||||||
|
|
||||||
|
/// Server is stopping.
|
||||||
|
Stopping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// From u8, panics if invalid.
|
||||||
|
pub fn from_u8(state: u8) -> Self {
|
||||||
|
match state {
|
||||||
|
0 => Self::Stopped,
|
||||||
|
1 => Self::Starting,
|
||||||
|
2 => Self::Started,
|
||||||
|
3 => Self::Stopping,
|
||||||
|
_ => panic!("invalid State u8"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To u8.
|
||||||
|
pub fn to_u8(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Stopped => 0,
|
||||||
|
Self::Starting => 1,
|
||||||
|
Self::Started => 2,
|
||||||
|
Self::Stopping => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Invoke server command, store PID and wait for it to quit.
|
/// Invoke server command, store PID and wait for it to quit.
|
||||||
pub async fn invoke_server_cmd(
|
pub async fn invoke_server_cmd(
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
@@ -380,7 +421,7 @@ pub async fn invoke_server_cmd(
|
|||||||
cmd.kill_on_drop(true);
|
cmd.kill_on_drop(true);
|
||||||
|
|
||||||
// Set working directory
|
// Set working directory
|
||||||
if let Some(ref dir) = config.server.directory {
|
if let Some(ref dir) = ConfigServer::server_directory(&config) {
|
||||||
cmd.current_dir(dir);
|
cmd.current_dir(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +447,11 @@ pub async fn invoke_server_cmd(
|
|||||||
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
|
debug!(target: "lazymc", "Server process stopped successfully ({})", status);
|
||||||
false
|
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) => {
|
Ok(status) => {
|
||||||
warn!(target: "lazymc", "Server process stopped with error code ({})", status);
|
warn!(target: "lazymc", "Server process stopped with error code ({})", status);
|
||||||
state.state() == State::Started
|
state.state() == State::Started
|
||||||
@@ -461,13 +507,8 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RCON address
|
|
||||||
let mut addr = config.server.address;
|
|
||||||
addr.set_port(config.rcon.port);
|
|
||||||
let addr = addr.to_string();
|
|
||||||
|
|
||||||
// Create RCON client
|
// 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,
|
Ok(rcon) => rcon,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(target: "lazymc", "Failed to RCON server to sleep: {}", err);
|
error!(target: "lazymc", "Failed to RCON server to sleep: {}", err);
|
||||||
@@ -485,11 +526,11 @@ async fn stop_server_rcon(config: &Config, server: &Server) -> bool {
|
|||||||
server.rcon_last_stop.lock().await.replace(Instant::now());
|
server.rcon_last_stop.lock().await.replace(Instant::now());
|
||||||
server.update_state(State::Stopping, config).await;
|
server.update_state(State::Stopping, config).await;
|
||||||
|
|
||||||
drop(rcon_lock);
|
|
||||||
|
|
||||||
// Gracefully close connection
|
// Gracefully close connection
|
||||||
rcon.close().await;
|
rcon.close().await;
|
||||||
|
|
||||||
|
drop(rcon_lock);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
119
src/service/ban_reload.rs
Normal file
119
src/service/ban_reload.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
|
|
||||||
|
use crate::config::{Config, Server as ConfigServer};
|
||||||
|
use crate::mc::ban;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
/// File debounce time.
|
||||||
|
const WATCH_DEBOUNCE: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
|
/// Service to reload banned IPs when its file changes.
|
||||||
|
pub fn service(config: Arc<Config>, server: Arc<Server>) {
|
||||||
|
// TODO: check what happens when file doesn't exist at first?
|
||||||
|
|
||||||
|
// Ensure we need to reload banned IPs
|
||||||
|
if !config.server.block_banned_ips && !config.server.drop_banned_ips {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure server directory is set, it must exist
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine file path, ensure it exists
|
||||||
|
let path = dir.join(crate::mc::ban::FILE);
|
||||||
|
if !path.is_file() {
|
||||||
|
warn!(target: "lazymc", "Not blocking banned IPs, {} file does not exist", ban::FILE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load banned IPs once
|
||||||
|
match ban::load(&path) {
|
||||||
|
Ok(ips) => server.set_banned_ips_blocking(ips),
|
||||||
|
Err(err) => {
|
||||||
|
error!(target: "lazymc", "Failed to load banned IPs from {}: {}", ban::FILE, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show warning if 127.0.0.1 is banned
|
||||||
|
if server.is_banned_ip_blocking(&("127.0.0.1".parse().unwrap())) {
|
||||||
|
warn!(target: "lazymc", "Local address 127.0.0.1 IP banned, probably not what you want");
|
||||||
|
warn!(target: "lazymc", "Use '/pardon-ip 127.0.0.1' on the server to unban");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep watching
|
||||||
|
while watch(&server, &path) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Watch the given file.
|
||||||
|
fn watch(server: &Server, path: &Path) -> bool {
|
||||||
|
// The file must exist
|
||||||
|
if !path.is_file() {
|
||||||
|
warn!(target: "lazymc", "File {} does not exist, not watching changes", ban::FILE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create watcher for banned IPs file
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let mut watcher =
|
||||||
|
watcher(tx, WATCH_DEBOUNCE).expect("failed to create watcher for banned-ips.json");
|
||||||
|
if let Err(err) = watcher.watch(path, RecursiveMode::NonRecursive) {
|
||||||
|
error!(target: "lazymc", "An error occured while creating watcher for {}: {}", ban::FILE, err);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Take next event
|
||||||
|
let event = rx.recv().unwrap();
|
||||||
|
|
||||||
|
// Decide whether to reload and rewatch
|
||||||
|
let (reload, rewatch) = match event {
|
||||||
|
// Reload on write
|
||||||
|
DebouncedEvent::NoticeWrite(_) | DebouncedEvent::Write(_) => (true, false),
|
||||||
|
|
||||||
|
// Reload and rewatch on rename/remove
|
||||||
|
DebouncedEvent::NoticeRemove(_)
|
||||||
|
| DebouncedEvent::Remove(_)
|
||||||
|
| DebouncedEvent::Rename(_, _)
|
||||||
|
| DebouncedEvent::Rescan
|
||||||
|
| DebouncedEvent::Create(_) => {
|
||||||
|
trace!(target: "lazymc", "File banned-ips.json removed, trying to rewatch after 1 second");
|
||||||
|
thread::sleep(WATCH_DEBOUNCE);
|
||||||
|
(true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore chmod changes
|
||||||
|
DebouncedEvent::Chmod(_) => (false, false),
|
||||||
|
|
||||||
|
// Rewatch on error
|
||||||
|
DebouncedEvent::Error(_, _) => (false, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reload banned IPs
|
||||||
|
if reload {
|
||||||
|
info!(target: "lazymc", "Reloading list of banned IPs...");
|
||||||
|
match ban::load(path) {
|
||||||
|
Ok(ips) => server.set_banned_ips_blocking(ips),
|
||||||
|
Err(err) => {
|
||||||
|
error!(target: "lazymc", "Failed reload list of banned IPs from {}: {}", ban::FILE, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewatch
|
||||||
|
if rewatch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod ban_reload;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
|
@@ -6,8 +6,8 @@ use futures::FutureExt;
|
|||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::proto::Client;
|
use crate::proto::client::Client;
|
||||||
use crate::proxy;
|
use crate::proxy::{self, ProxyHeader};
|
||||||
use crate::server::{self, Server};
|
use crate::server::{self, Server};
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::status;
|
use crate::status;
|
||||||
@@ -49,6 +49,10 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> {
|
|||||||
// Spawn server monitor and signal handler services
|
// Spawn server monitor and signal handler services
|
||||||
tokio::spawn(service::monitor::service(config.clone(), server.clone()));
|
tokio::spawn(service::monitor::service(config.clone(), server.clone()));
|
||||||
tokio::spawn(service::signal::service(config.clone(), server.clone()));
|
tokio::spawn(service::signal::service(config.clone(), server.clone()));
|
||||||
|
tokio::task::spawn_blocking({
|
||||||
|
let (config, server) = (config.clone(), server.clone());
|
||||||
|
|| service::ban_reload::service(config, server)
|
||||||
|
});
|
||||||
|
|
||||||
// Initiate server start
|
// Initiate server start
|
||||||
if config.server.wake_on_start {
|
if config.server.wake_on_start {
|
||||||
@@ -66,19 +70,37 @@ pub async fn service(config: Arc<Config>) -> Result<(), ()> {
|
|||||||
/// Route inbound TCP stream to correct service, spawning a new task.
|
/// Route inbound TCP stream to correct service, spawning a new task.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn route(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) {
|
fn route(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) {
|
||||||
let should_proxy = server.state() == server::State::Started && !config.lockout.enabled;
|
// Get user peer address
|
||||||
|
let peer = match inbound.peer_addr() {
|
||||||
|
Ok(peer) => peer,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(target: "lazymc", "Connection from unknown peer address, disconnecting: {}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check ban state, just drop connection if enabled
|
||||||
|
let banned = server.is_banned_ip_blocking(&peer.ip());
|
||||||
|
if config.server.drop_banned_ips {
|
||||||
|
info!(target: "lazymc", "Connection from banned IP {}, dropping", peer.ip());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route connection through proper channel
|
||||||
|
let should_proxy =
|
||||||
|
!banned && server.state() == server::State::Started && !config.lockout.enabled;
|
||||||
if should_proxy {
|
if should_proxy {
|
||||||
route_proxy(inbound, config)
|
route_proxy(inbound, config)
|
||||||
} else {
|
} else {
|
||||||
route_status(inbound, config, server)
|
route_status(inbound, config, server, peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route inbound TCP stream to status server, spawning a new task.
|
/// Route inbound TCP stream to status server, spawning a new task.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) {
|
fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>, peer: SocketAddr) {
|
||||||
// When server is not online, spawn a status server
|
// When server is not online, spawn a status server
|
||||||
let client = Client::default();
|
let client = Client::new(peer);
|
||||||
let service = status::serve(client, inbound, config, server).map(|r| {
|
let service = status::serve(client, inbound, config, server).map(|r| {
|
||||||
if let Err(err) = r {
|
if let Err(err) = r {
|
||||||
warn!(target: "lazymc", "Failed to serve status: {:?}", err);
|
warn!(target: "lazymc", "Failed to serve status: {:?}", err);
|
||||||
@@ -92,7 +114,12 @@ fn route_status(inbound: TcpStream, config: Arc<Config>, server: Arc<Server>) {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
|
fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
|
||||||
// When server is online, proxy all
|
// 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 {
|
if let Err(err) = r {
|
||||||
warn!(target: "lazymc", "Failed to proxy: {}", err);
|
warn!(target: "lazymc", "Failed to proxy: {}", err);
|
||||||
}
|
}
|
||||||
@@ -104,15 +131,25 @@ fn route_proxy(inbound: TcpStream, config: Arc<Config>) {
|
|||||||
/// Route inbound TCP stream to proxy with queued data, spawning a new task.
|
/// Route inbound TCP stream to proxy with queued data, spawning a new task.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn route_proxy_queue(inbound: TcpStream, config: Arc<Config>, queue: BytesMut) {
|
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.
|
/// Route inbound TCP stream to proxy with given address and queued data, spawning a new task.
|
||||||
#[inline]
|
#[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
|
// When server is online, proxy all
|
||||||
let service = async move {
|
let service = async move {
|
||||||
proxy::proxy_with_queue(inbound, addr, &queue)
|
proxy::proxy_with_queue(inbound, proxy_header, addr, &queue)
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
if let Err(err) = r {
|
if let Err(err) = r {
|
||||||
warn!(target: "lazymc", "Failed to proxy: {}", err);
|
warn!(target: "lazymc", "Failed to proxy: {}", err);
|
||||||
|
322
src/status.rs
322
src/status.rs
@@ -1,6 +1,4 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use minecraft_protocol::data::chat::{Message, Payload};
|
use minecraft_protocol::data::chat::{Message, Payload};
|
||||||
@@ -8,19 +6,29 @@ use minecraft_protocol::data::server_status::*;
|
|||||||
use minecraft_protocol::decoder::Decoder;
|
use minecraft_protocol::decoder::Decoder;
|
||||||
use minecraft_protocol::encoder::Encoder;
|
use minecraft_protocol::encoder::Encoder;
|
||||||
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
use minecraft_protocol::version::v1_14_4::handshake::Handshake;
|
||||||
use minecraft_protocol::version::v1_14_4::login::{LoginDisconnect, LoginStart};
|
use minecraft_protocol::version::v1_14_4::login::LoginStart;
|
||||||
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
use minecraft_protocol::version::v1_14_4::status::StatusResponse;
|
||||||
use tokio::io::{self, AsyncWriteExt};
|
use tokio::fs;
|
||||||
use tokio::net::tcp::WriteHalf;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::time;
|
|
||||||
|
|
||||||
use crate::config::*;
|
use crate::config::{Config, Server as ConfigServer};
|
||||||
#[cfg(feature = "lobby")]
|
use crate::join;
|
||||||
use crate::lobby;
|
use crate::mc::favicon;
|
||||||
use crate::proto::{self, Client, ClientInfo, ClientState, RawPacket};
|
use crate::proto::action;
|
||||||
use crate::server::{self, Server, State};
|
use crate::proto::client::{Client, ClientInfo, ClientState};
|
||||||
use crate::service;
|
use crate::proto::packet::{self, RawPacket};
|
||||||
|
use crate::proto::packets;
|
||||||
|
use crate::server::{self, Server};
|
||||||
|
|
||||||
|
/// The ban message prefix.
|
||||||
|
const BAN_MESSAGE_PREFIX: &str = "Your IP address is banned from this server.\nReason: ";
|
||||||
|
|
||||||
|
/// 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.
|
/// Proxy the given inbound stream to a target address.
|
||||||
// TODO: do not drop error here, return Box<dyn Error>
|
// TODO: do not drop error here, return Box<dyn Error>
|
||||||
@@ -35,16 +43,13 @@ pub async fn serve(
|
|||||||
// Incoming buffer and packet holding queue
|
// Incoming buffer and packet holding queue
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
|
|
||||||
// Remember inbound packets, used for client holding and forwarding
|
// Remember inbound packets, track client info
|
||||||
let remember_inbound = config.join.methods.contains(&Method::Hold)
|
|
||||||
|| config.join.methods.contains(&Method::Forward);
|
|
||||||
let mut inbound_history = BytesMut::new();
|
let mut inbound_history = BytesMut::new();
|
||||||
|
|
||||||
let mut client_info = ClientInfo::empty();
|
let mut client_info = ClientInfo::empty();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Read packet from stream
|
// Read packet from stream
|
||||||
let (packet, raw) = match proto::read_packet(&client, &mut buf, &mut reader).await {
|
let (packet, raw) = match packet::read_packet(&client, &mut buf, &mut reader).await {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
Ok(None) => break,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -58,7 +63,7 @@ pub async fn serve(
|
|||||||
|
|
||||||
// Hijack handshake
|
// Hijack handshake
|
||||||
if client_state == ClientState::Handshake
|
if client_state == ClientState::Handshake
|
||||||
&& packet.id == proto::packets::handshake::SERVER_HANDSHAKE
|
&& packet.id == packets::handshake::SERVER_HANDSHAKE
|
||||||
{
|
{
|
||||||
// Parse handshake
|
// Parse handshake
|
||||||
let handshake = match Handshake::decode(&mut packet.data.as_slice()) {
|
let handshake = match Handshake::decode(&mut packet.data.as_slice()) {
|
||||||
@@ -84,8 +89,8 @@ pub async fn serve(
|
|||||||
.replace(handshake.protocol_version);
|
.replace(handshake.protocol_version);
|
||||||
client.set_state(new_state);
|
client.set_state(new_state);
|
||||||
|
|
||||||
// If login handshake and holding is enabled, hold packets
|
// If loggin in with handshake, remember inbound
|
||||||
if new_state == ClientState::Login && remember_inbound {
|
if new_state == ClientState::Login {
|
||||||
inbound_history.extend(raw);
|
inbound_history.extend(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +98,8 @@ pub async fn serve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hijack server status packet
|
// Hijack server status packet
|
||||||
if client_state == ClientState::Status && packet.id == proto::packets::status::SERVER_STATUS
|
if client_state == ClientState::Status && packet.id == packets::status::SERVER_STATUS {
|
||||||
{
|
let server_status = server_status(&client_info, &config, &server).await;
|
||||||
let server_status = server_status(&config, &server).await;
|
|
||||||
let packet = StatusResponse { server_status };
|
let packet = StatusResponse { server_status };
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
@@ -108,15 +112,13 @@ pub async fn serve(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hijack ping packet
|
// Hijack ping packet
|
||||||
if client_state == ClientState::Status && packet.id == proto::packets::status::SERVER_PING {
|
if client_state == ClientState::Status && packet.id == packets::status::SERVER_PING {
|
||||||
writer.write_all(&raw).await.map_err(|_| ())?;
|
writer.write_all(&raw).await.map_err(|_| ())?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack login start
|
// Hijack login start
|
||||||
if client_state == ClientState::Login
|
if client_state == ClientState::Login && packet.id == packets::login::SERVER_LOGIN_START {
|
||||||
&& packet.id == proto::packets::login::SERVER_LOGIN_START
|
|
||||||
{
|
|
||||||
// Try to get login username, update client info
|
// Try to get login username, update client info
|
||||||
// TODO: we should always parse this packet successfully
|
// TODO: we should always parse this packet successfully
|
||||||
let username = LoginStart::decode(&mut packet.data.as_slice())
|
let username = LoginStart::decode(&mut packet.data.as_slice())
|
||||||
@@ -132,100 +134,57 @@ pub async fn serve(
|
|||||||
}
|
}
|
||||||
None => info!(target: "lazymc", "Kicked player because lockout is enabled"),
|
None => info!(target: "lazymc", "Kicked player because lockout is enabled"),
|
||||||
}
|
}
|
||||||
kick(&client, &config.lockout.message, &mut writer).await?;
|
action::kick(&client, &config.lockout.message, &mut writer).await?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kick if client is banned
|
||||||
|
if let Some(ban) = server.ban_entry(&client.peer.ip()).await {
|
||||||
|
if ban.is_banned() {
|
||||||
|
let msg = if let Some(reason) = ban.reason {
|
||||||
|
info!(target: "lazymc", "Login from banned IP {} ({}), disconnecting", client.peer.ip(), &reason);
|
||||||
|
reason.to_string()
|
||||||
|
} else {
|
||||||
|
info!(target: "lazymc", "Login from banned IP {}, disconnecting", client.peer.ip());
|
||||||
|
DEFAULT_BAN_REASON.to_string()
|
||||||
|
};
|
||||||
|
action::kick(
|
||||||
|
&client,
|
||||||
|
&format!("{}{}", BAN_MESSAGE_PREFIX, msg),
|
||||||
|
&mut writer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start server if not starting yet
|
// Start server if not starting yet
|
||||||
Server::start(config.clone(), server.clone(), username).await;
|
Server::start(config.clone(), server.clone(), username).await;
|
||||||
|
|
||||||
// Use join occupy methods
|
// Remember inbound packets
|
||||||
for method in &config.join.methods {
|
inbound_history.extend(&raw);
|
||||||
match method {
|
inbound_history.extend(&buf);
|
||||||
// Kick method, immediately kick client
|
|
||||||
Method::Kick => {
|
|
||||||
trace!(target: "lazymc", "Using kick method to occupy joining client");
|
|
||||||
|
|
||||||
// Select message and kick
|
// Build inbound packet queue with everything from login start (including this)
|
||||||
let msg = match server.state() {
|
let mut login_queue = BytesMut::with_capacity(raw.len() + buf.len());
|
||||||
server::State::Starting
|
login_queue.extend(&raw);
|
||||||
| server::State::Stopped
|
login_queue.extend(&buf);
|
||||||
| server::State::Started => &config.join.kick.starting,
|
|
||||||
server::State::Stopping => &config.join.kick.stopping,
|
|
||||||
};
|
|
||||||
kick(&client, msg, &mut writer).await?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hold method, hold client connection while server starts
|
// Buf is fully consumed here
|
||||||
Method::Hold => {
|
buf.clear();
|
||||||
trace!(target: "lazymc", "Using hold method to occupy joining client");
|
|
||||||
|
|
||||||
// Server must be starting
|
// Start occupying client
|
||||||
if server.state() != State::Starting {
|
join::occupy(
|
||||||
continue;
|
client,
|
||||||
}
|
client_info,
|
||||||
|
config,
|
||||||
// Hold login packet and remaining read bytes
|
server,
|
||||||
inbound_history.extend(&raw);
|
inbound,
|
||||||
inbound_history.extend(buf.split_off(0));
|
inbound_history,
|
||||||
|
login_queue,
|
||||||
// Start holding
|
)
|
||||||
if hold(&config, &server).await? {
|
.await?;
|
||||||
service::server::route_proxy_queue(inbound, config, inbound_history);
|
return Ok(());
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward method, forward client connection while server starts
|
|
||||||
Method::Forward => {
|
|
||||||
trace!(target: "lazymc", "Using forward method to occupy joining client");
|
|
||||||
|
|
||||||
// Hold login packet and remaining read bytes
|
|
||||||
inbound_history.extend(&raw);
|
|
||||||
inbound_history.extend(buf.split_off(0));
|
|
||||||
|
|
||||||
// Forward client
|
|
||||||
debug!(target: "lazymc", "Forwarding client to {:?}!", config.join.forward.address);
|
|
||||||
|
|
||||||
service::server::route_proxy_address_queue(
|
|
||||||
inbound,
|
|
||||||
config.join.forward.address,
|
|
||||||
inbound_history,
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
|
|
||||||
// TODO: do not consume client here, allow other join method on fail
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lobby method, keep client in lobby while server starts
|
|
||||||
#[cfg(feature = "lobby")]
|
|
||||||
Method::Lobby => {
|
|
||||||
trace!(target: "lazymc", "Using lobby method to occupy joining client");
|
|
||||||
|
|
||||||
// Build queue with login packet and any additionally received
|
|
||||||
let mut queue = BytesMut::with_capacity(raw.len() + buf.len());
|
|
||||||
queue.extend(raw);
|
|
||||||
queue.extend(buf.split_off(0));
|
|
||||||
|
|
||||||
// Start lobby
|
|
||||||
lobby::serve(client, client_info, inbound, config, server, queue).await?;
|
|
||||||
return Ok(());
|
|
||||||
// TODO: do not consume client here, allow other join method on fail
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lobby method, keep client in lobby while server starts
|
|
||||||
#[cfg(not(feature = "lobby"))]
|
|
||||||
Method::Lobby => {
|
|
||||||
error!(target: "lazymc", "Lobby join method not supported in this lazymc build");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(target: "lazymc", "No method left to occupy joining client, disconnecting");
|
|
||||||
|
|
||||||
// Done occupying client, just disconnect
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show unhandled packet warning
|
// Show unhandled packet warning
|
||||||
@@ -234,99 +193,18 @@ pub async fn serve(
|
|||||||
debug!(target: "lazymc", "- Packet ID: {}", packet.id);
|
debug!(target: "lazymc", "- Packet ID: {}", packet.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gracefully close connection
|
|
||||||
match writer.shutdown().await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
|
||||||
Err(_) => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hold a client while server starts.
|
|
||||||
///
|
|
||||||
/// Returns holding status. `true` if client is held and it should be proxied, `false` it was held
|
|
||||||
/// but it timed out.
|
|
||||||
pub async fn hold<'a>(config: &Config, server: &Server) -> Result<bool, ()> {
|
|
||||||
trace!(target: "lazymc", "Started holding client");
|
|
||||||
|
|
||||||
// A task to wait for suitable server state
|
|
||||||
// Waits for started state, errors if stopping/stopped state is reached
|
|
||||||
let task_wait = async {
|
|
||||||
let mut state = server.state_receiver();
|
|
||||||
loop {
|
|
||||||
// Wait for state change
|
|
||||||
state.changed().await.unwrap();
|
|
||||||
|
|
||||||
match state.borrow().deref() {
|
|
||||||
// Still waiting on server start
|
|
||||||
State::Starting => {
|
|
||||||
trace!(target: "lazymc", "Server not ready, holding client for longer");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server started, start relaying and proxy
|
|
||||||
State::Started => {
|
|
||||||
break true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server stopping, this shouldn't happen, kick
|
|
||||||
State::Stopping => {
|
|
||||||
warn!(target: "lazymc", "Server stopping for held client, disconnecting");
|
|
||||||
break false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server stopped, this shouldn't happen, disconnect
|
|
||||||
State::Stopped => {
|
|
||||||
error!(target: "lazymc", "Server stopped for held client, disconnecting");
|
|
||||||
break false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for server state with timeout
|
|
||||||
let timeout = Duration::from_secs(config.join.hold.timeout as u64);
|
|
||||||
match time::timeout(timeout, task_wait).await {
|
|
||||||
// Relay client to proxy
|
|
||||||
Ok(true) => {
|
|
||||||
info!(target: "lazymc", "Server ready for held client, relaying to server");
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server stopping/stopped, this shouldn't happen, kick
|
|
||||||
Ok(false) => {
|
|
||||||
warn!(target: "lazymc", "Server stopping for held client");
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout reached, kick with starting message
|
|
||||||
Err(_) => {
|
|
||||||
warn!(target: "lazymc", "Held client reached timeout of {}s", config.join.hold.timeout);
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kick client with a message.
|
|
||||||
///
|
|
||||||
/// Should close connection afterwards.
|
|
||||||
async fn kick(client: &Client, msg: &str, writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
|
||||||
let packet = LoginDisconnect {
|
|
||||||
reason: Message::new(Payload::text(msg)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
|
||||||
|
|
||||||
let response = RawPacket::new(proto::packets::login::CLIENT_DISCONNECT, data).encode(client)?;
|
|
||||||
writer.write_all(&response).await.map_err(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build server status object to respond to client with.
|
/// 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 status = server.status().await;
|
||||||
|
let server_state = server.state();
|
||||||
|
|
||||||
|
// Respond with real server status if started
|
||||||
|
if server_state == server::State::Started && status.is_some() {
|
||||||
|
return status.as_ref().unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
// Select version and player max from last known server status
|
// Select version and player max from last known server status
|
||||||
let (version, max) = match status.as_ref() {
|
let (version, max) = match status.as_ref() {
|
||||||
@@ -345,7 +223,7 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
|
|||||||
if config.motd.from_server && status.is_some() {
|
if config.motd.from_server && status.is_some() {
|
||||||
status.as_ref().unwrap().description.clone()
|
status.as_ref().unwrap().description.clone()
|
||||||
} else {
|
} else {
|
||||||
Message::new(Payload::text(match server.state() {
|
Message::new(Payload::text(match server_state {
|
||||||
server::State::Stopped | server::State::Started => &config.motd.sleeping,
|
server::State::Stopped | server::State::Started => &config.motd.sleeping,
|
||||||
server::State::Starting => &config.motd.starting,
|
server::State::Starting => &config.motd.starting,
|
||||||
server::State::Stopping => &config.motd.stopping,
|
server::State::Stopping => &config.motd.stopping,
|
||||||
@@ -353,6 +231,17 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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
|
// Build status resposne
|
||||||
ServerStatus {
|
ServerStatus {
|
||||||
version,
|
version,
|
||||||
@@ -362,5 +251,36 @@ async fn server_status(config: &Config, server: &Server) -> ServerStatus {
|
|||||||
max,
|
max,
|
||||||
sample: vec![],
|
sample: vec![],
|
||||||
},
|
},
|
||||||
|
favicon,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get server status favicon.
|
||||||
|
///
|
||||||
|
/// 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 ConfigServer::server_directory(config) {
|
||||||
|
Some(dir) => dir,
|
||||||
|
None => return favicon::default_favicon(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Server icon file, ensure it exists
|
||||||
|
let path = dir.join(SERVER_ICON_FILE);
|
||||||
|
if !path.is_file() {
|
||||||
|
return favicon::default_favicon();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read icon data
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
favicon::encode_favicon(&data)
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod serde;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
30
src/util/serde.rs
Normal file
30
src/util/serde.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
|
|
||||||
|
use serde::de::{Error, Unexpected};
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
|
/// Deserialize a `Vec` into a `HashMap` by key.
|
||||||
|
pub fn to_socket_addrs<'de, D>(d: D) -> Result<SocketAddr, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
// Deserialize string
|
||||||
|
let addr = String::deserialize(d)?;
|
||||||
|
|
||||||
|
// Try to socket address to resolve
|
||||||
|
match addr.to_socket_addrs() {
|
||||||
|
Ok(mut addr) => {
|
||||||
|
if let Some(addr) = addr.next() {
|
||||||
|
return Ok(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
dbg!(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse raw IP address
|
||||||
|
addr.parse().map_err(|_| {
|
||||||
|
Error::invalid_value(Unexpected::Str(&addr), &"IP or resolvable host and port")
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user