123 Commits
Author SHA1 Message Date
Dylan Keir 133cef037e 2.7.0 - Update to support Velocity's new class name 2024-01-22 22:55:59 +00:00
Dylan Keir bb7511d37f Ignore new ServerData packet that was throwing errors 2022-09-19 15:41:34 -04:00
Dylan Keir 78666de764 Merge pull request #86 from gp-Airee/patch-1
Correct typo in handshake failure kick
2022-07-20 19:52:27 +01:00
Graham 0534b8eae8 Contract An Administrator 2022-07-20 20:49:58 +02:00
Dylan Keir 1af7f1efde 2.6.0, too many changes to list, will be in release readme 2022-06-28 03:29:13 +01:00
Dylan Keir 41ff3f412c (2.5.6) re-introduce PreLoginEvent 2022-02-02 18:52:53 +00:00
Dylan Keir cfbd6ffac5 Support Velocity's new LoginInboundConnection class. 2021-11-21 17:13:47 +00:00
Dylan Keir cf97e37632 Add try/catch to java version check 2021-10-31 15:43:08 +00:00
Dylan Keir 1629334e71 fix issue with signature verification due to erroneous event, 2.5.4 release 2021-10-27 22:17:57 +01:00
Dylan Keir 8cb66fc87a add description to plugin.yml 2021-10-11 13:30:30 +01:00
Dylan Keir e868bab503 Geyser support with BungeeCord & Velocity is now fully functional 2021-10-11 13:25:28 +01:00
Dylan Keir 2daad65b33 fix 2021-08-28 05:49:38 +01:00
Dylan Keir 846f4d0a6e 2.5.4: initial geyser/floodgate v2 support with only-allow-proxy-connections 2021-08-28 05:43:47 +01:00
Dylan Keir c07b71c5b1 re-introduce java 8 support due to high demand 2021-08-02 04:37:11 +01:00
Dylan Keir 4b13ac2ca1 2.5.2 & fix a java16/gradle7 build issue 2021-07-28 20:31:15 +01:00
Steven Ferguson 860fba8d6e Merge pull request #60 from xPlumpOrange/master
General Bug and Typo Fixes
2021-06-27 17:27:51 -06:00
Plump Orange 99cca8aa33 Merge branch 'TCPShield:master' into master 2021-06-27 19:15:48 -04:00
xPlumpOrange ad2a29de0f General Bug and Typo Fixes
Removed ReflectionUtil logic that caused issues. Added cancel call in Paper implementation to fix Paper issues. Upgraded minimum Java version to 11 and added checks to make sure the plugin is ran properly. Fixed old/invalid links in README. (Merged PR's of other users)
2021-06-27 19:14:58 -04:00
Steven Ferguson 17a28b1c57 Merge pull request #48 from xPlumpOrange/master
Fixed Bukkit Config Loading and ProtocolLib Implementation
2021-05-10 19:26:24 -06:00
xPlumpOrange eb8663f5b8 Fixed Bukkit Config Loading and ProtocolLib Implementation
Changed resource streaming for the Bukkit implementation of the config to Bukkit's resource streamer. Also fixed an issue with the ProtocolLib implementation where it throws an error instead of warning and disabling when ProtocolLib is not installed.
2021-05-10 21:01:26 -04:00
Steven Ferguson be2fe38744 Merge pull request #47 from xPlumpOrange/master
Added IP Whitelisting and Smarter Configs
2021-05-08 19:05:51 -06:00
xPlumpOrange e740a28f77 Removed Repeated Information in Exception
Removed payload information in InvalidPayloadException since the same information is displayed in the debugger.
2021-05-08 21:04:06 -04:00
xPlumpOrange d8a755035c Fixed logic issue with backwards support of old v4 system
Added a skip to the IP validation if the payload length is correct so users with old unsupported v4 ip whitelists dont have issues.
2021-05-08 20:48:17 -04:00
xPlumpOrange 188d541c4d Added Node Checking for Smarter Configs
Implemented node checks during config loading to stop the config api from providing null and causing other unrelated initialization errors and stopping auto config reset.
2021-05-08 20:22:42 -04:00
Plump Orange 40138413bb Merge branch 'TCPShield:master' into master 2021-05-08 18:58:59 -04:00
xPlumpOrange 2475bdfabd Added IP Whitelisting and Smarter Configs
Integrated a CIDR matching IP whitelist as seen in v2.4. Implemented forced config methods for resetting, loading, and reloading for auto resetting on errors and future support for reloading.
2021-05-08 18:58:05 -04:00
Steven Ferguson 71ba01d8e7 Merge pull request #46 from xPlumpOrange/master
2.5 Release
2021-05-07 15:00:56 -06:00
xPlumpOrange 8416dbe4e3 2.5 Release 2021-05-07 16:54:01 -04:00
Steven Ferguson 863fd644cf Merge pull request #42 from AoElite/preloginfix
Velocity PreLoginEvent Fix
2021-04-16 01:02:55 -06:00
AoElite f91a5c54b6 fix prelogin 2021-04-14 07:22:04 -04:00
Paul Zhang 44715187c4 fixes spelling mistake 2021-02-25 13:31:03 +01:00
Paul Zhang 75e30ec6b3 we're not in rust 2021-02-24 14:53:24 +01:00
Paul Zhang f0f1c53ba8 Lazily initialize the final field 2021-02-23 18:20:32 +01:00
Paul Zhang 5779306940 Merge pull request #33 from SlingSMP/master
Remove Legacy API
2021-01-19 13:59:58 +01:00
Ben Kerllenevich d031708308 Remove Legacy 2021-01-19 06:40:40 -05:00
Paul Zhang 46f7e9385b Merge pull request #32 from TCPShield/bugfix/event-priority
Adjusts event priority
2021-01-11 23:44:48 +01:00
Paul Zhang f100c0578a adjust event priority 2021-01-08 23:54:30 +01:00
Paul Zhang 15c0543114 removes two unused fields 2021-01-08 23:38:13 +01:00
Paul Zhang a1d6664906 removes second [TCPShield]-prefix on ip whitelist fetching 2020-12-27 00:34:52 +01:00
Paul Zhang 22b0b7065d Merge pull request #28 from TCPShield/feature-revise-gitignore
Update .gitignore
2020-12-24 17:13:02 +01:00
Paul Zhang e66f4df4ee Merge pull request #29 from RoboMWM/patch-1
contract an administrator
2020-12-23 20:04:36 +01:00
RoboMWM 74bc2b069c Well, contracts can be kewl too 2020-12-23 11:01:40 -08:00
Paul Zhang 637c64916b Update .gitignore 2020-12-23 17:14:43 +01:00
Paul Zhang 0a8d6295b2 Merge pull request #27 from TCPShield/bugfix-http-ok-check
Adds a check if the webserver returns 200 on pulling the IP list
2020-12-23 16:58:41 +01:00
Paul Zhang 1d7f2c368a changes line formatting 2020-12-23 16:55:05 +01:00
Paul Zhang 086bdd99d5 adds additional checks for validity of the returned string 2020-12-23 16:54:04 +01:00
Paul Zhang d01aa5efc4 Adds a check if the webserver returns 200 on pulling the IP list 2020-12-23 16:43:16 +01:00
Paul Zhang d67ece7a0d Create LICENSE 2020-12-19 20:07:06 +01:00
Paul Zhang 615e4b2289 Update README.md
Updates user link
2020-12-12 01:18:23 +01:00
Paul Zhang ddf722c069 Fixes testing 2020-12-06 20:25:18 +01:00
Fuzzlemann 1b716d5e18 Merge remote-tracking branch 'origin/master' 2020-12-04 15:21:56 +01:00
Fuzzlemann 839d625ce9 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:21:47 +01:00
Fuzzlemann 8a01e915b1 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:20:30 +01:00
Fuzzlemann 32d422a952 Fixes raw IP matching 2020-12-04 13:45:22 +01:00
Fuzzlemann c5cd43f2fa Merge remote-tracking branch 'origin/master' 2020-12-02 14:12:51 +01:00
Fuzzlemann d69d4f7ba8 Removes unused config keys 2020-12-02 14:12:35 +01:00
Paul Zhang 52325a8a2c Merge pull request #24 from TCPShield/ip-validation
TCPShield v2.4
2020-12-01 23:22:08 +01:00
Fuzzlemann b07fe80979 Future-proofs HandshakePacketHandler for possible proxy-side changes in the handshake hostname data 2020-12-01 23:13:39 +01:00
Fuzzlemann c6471d8e15 Fixes compatibility on some BungeeCord forks like FlameCord 2020-12-01 23:11:48 +01:00
Fuzzlemann 0dd3c25421 Removes the empty line on fetching the TCPShield IP list 2020-12-01 22:14:23 +01:00
Fuzzlemann 42530caa57 Reverts re-activation of native Paper support 2020-11-30 17:11:53 +01:00
Fuzzlemann e58319cabe Bumps version to v2.4 2020-11-30 17:11:23 +01:00
Fuzzlemann 1de480d575 Merge remote-tracking branch 'origin/ip-validation' into ip-validation 2020-11-29 19:34:30 +01:00
Fuzzlemann 1959106ac8 Fixes that the updated IPs are appended instead of replaced 2020-11-29 19:33:50 +01:00
Paul Zhang 5750391e43 Update README.md
Updated because of the re-introduction of native paper support
2020-11-29 15:30:00 +01:00
Fuzzlemann 00f50110fe Adds checks for the origin IP (if it's originating from TCPShield nodes) in replacement of signature checks + timestamp validation. Custom CIDR records can be added by creating a file in the "ip-whitelist" folder and adding one CIDR entry per line. 2020-11-29 15:16:11 +01:00
Paul Zhang e14d937a70 Merge pull request #16 from JasperJH/master
Fixes searchFieldByClass looping forever and incorrect cache keys
2020-11-05 23:19:10 +01:00
Fuzzlemann 3d5dab69bb Changes wrong static final field naming 2020-11-01 19:09:00 +01:00
Fuzzlemann a4302e4de8 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-11-01 18:20:37 +01:00
Fuzzlemann e859a1c95a Fixes ReflectionUtils#searchFieldByClass cache (#16 by JasperJH) 2020-11-01 18:19:16 +01:00
Fuzzlemann a1e9b84b47 Refactors some test strings in TestConstants.java 2020-11-01 18:17:47 +01:00
Fuzzlemann af998585e9 Adds Maven repository to build.gradle 2020-11-01 18:17:04 +01:00
Fuzzlemann 62b18b0842 Version bump to 2.3 2020-11-01 18:16:46 +01:00
Steven Ferguson 8c0d98fda4 Update README.md 2020-10-30 14:34:47 -06:00
JasperJH 78d22e627c Fixes searchFieldByClass looping forever and incorrect cache keys 2020-09-11 12:12:16 +02:00
Fuzzlemann a6dc0865f7 Fixes some compatibility issues with 1.16.2 where in some cases NetworkManagerServer is passed 2020-08-24 22:16:43 +02:00
Fuzzlemann 7907d8f731 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-08-13 11:27:28 +02:00
Fuzzlemann 32142f9961 Fixes some exceptions regarding legacy connections on Velocity 1.7 clients 2020-08-13 11:27:03 +02:00
Paul Zhang f8d32f5e96 Update README.md 2020-08-11 15:11:46 +02:00
Fuzzlemann f8da84de07 Fixes an IllegalArgumentException because of a missing SocketAddress field on some BungeeCord versions (= 1.7, e.g. zBungeeCord) 2020-08-11 13:19:40 +02:00
Fuzzlemann cafc278c32 Fixes 1.7 support on some BungeeCord versions 2020-08-10 15:10:25 +02:00
Fuzzlemann e958718bcb Fixes issues with compatibility on 1.7 2020-07-30 19:42:49 +02:00
Fuzzlemann 031c2ed091 Change -64 to the constant EventPriority.LOWEST 2020-07-25 13:03:01 +02:00
Fuzzlemann faf7568c81 Add Velocity event orders (priorities) to ensure better compatibility 2020-07-15 17:17:48 +02:00
Fuzzlemann 812f5c353f Fixes error message when ProtocolLib's not installed 2020-07-15 17:14:43 +02:00
Fuzzlemann 6216b0c01e Bump version to 2.2 2020-07-11 23:09:54 +02:00
Fuzzlemann 901ce0cfeb Fix naming of the static final field 2020-07-11 23:09:12 +02:00
Fuzzlemann a537099297 Fixes TCPShield on servers using Java versions higher than or equal to 12 2020-07-11 23:08:01 +02:00
Fuzzlemann 7a14a45338 Removes the modified logger on Bungee 2020-07-09 23:45:14 +02:00
Fuzzlemann 8f35cf9532 Fixes forced hosts in the Velocity implementation when the player is e.g. a forge user 2020-07-06 21:02:37 +02:00
Paul Zhang 39a977fa97 Merge pull request #8 from Fuzzlemann/pr
TCPShield v2.1
2020-07-06 18:02:25 +02:00
Fuzzlemann 2178727b21 Adds the timestamp validation mode to the warning 2020-07-06 17:58:49 +02:00
Fuzzlemann 553332c641 Bumps version to 2.1 2020-07-06 17:53:18 +02:00
Fuzzlemann 23c207e3b9 Temporarily disables native Paper support 2020-07-06 17:52:02 +02:00
Fuzzlemann bf40833d5d Fixes forced hosts with Velocity 2020-07-02 22:14:41 +02:00
Fuzzlemann 818df3cbb1 Changes Velocity player disconnect when the handshake is legacy as an error was thrown 2020-06-30 20:59:53 +02:00
Fuzzlemann fcc3ef4fef Fixes Velocity error messages on legacy pings 2020-06-25 18:11:50 +02:00
Ryan De Lap d39f520279 Add contributors section to README 2020-06-14 22:20:15 -05:00
Ryan De Lap d24a6a1a99 Merge pull request #6 from Fuzzlemann/pr
Major rewrite of TCPShield
2020-06-14 02:05:06 -05:00
Fuzzlemann c3b5d209ae Fixes a bug where the config generation on BungeeCord failed. 2020-06-13 00:58:13 +02:00
Fuzzlemann 2ca823411f Fix for the Bungee timeout issue many are facing.
This simple but mandatory commit prevents that the hostname of the server is resolved to its IP which can cause massive network latencies.
2020-06-11 02:25:34 +02:00
Fuzzlemann 197f908fc5 Fixes wrongly named packets 2020-05-28 16:57:23 +02:00
Fuzzlemann a784224592 Updates README.md 2020-05-27 17:00:13 +02:00
Fuzzlemann 57f3e37ff7 Revert "Update README.md"
This reverts commit 3dc3fcad
2020-05-27 16:40:48 +02:00
Fuzzlemann f4b56671b9 Changes server to google.com 2020-05-24 02:18:29 +02:00
Fuzzlemann ed0670c5c5 Implements automatic synchronization of time over HTTP 2020-05-24 00:34:34 +02:00
Fuzzlemann 801fca080a Adds an option for turning on / off timestamp validation in case someone does not have control over the host machine time 2020-05-23 22:35:21 +02:00
Fuzzlemann 00b79770bd Fixes mistakenly named bungeecord implementation of IConfig 2020-05-15 23:03:03 +02:00
Fuzzlemann 9aef0402f5 Adds testing and fixes some issues which were noticed while creating the tests, including:
- correct exception handling when verifying signatures
- also searching private fields when searching fields by class
- making modifier field accessible
2020-05-15 22:52:13 +02:00
Fuzzlemann 1b76e0e6bf Adds support for Velocity 2020-05-15 19:43:11 +02:00
Fuzzlemann 43bbf589de Fix config creation error with BungeeCord 2020-05-15 19:25:47 +02:00
Fuzzlemann 12b8f8da99 removes forgotten debug messages 2020-05-15 18:34:09 +02:00
Fuzzlemann 9cd06036bb Adds native paper support (does not require the installation of ProtocolLib) 2020-05-15 18:32:32 +02:00
Fuzzlemann d9e2e34196 Merge remote-tracking branch 'fork/master' 2020-05-13 00:56:51 +02:00
Fuzzlemann f6bf0346ff -> changes class names related to abstraction 2020-05-13 00:56:37 +02:00
Paul Zhang 3dc3fcad05 Update README.md 2020-05-13 00:05:04 +02:00
Fuzzlemann 60ae269f33 Major code restructuring in order to fulfill quality requirements
-> moves to Gradle
  -> major speedup of build times
  -> automatic adjustment of version in bungee.yml and plugin.yml based on version given in the gradle build file
-> implements abstraction layers
  -> unification of Bungee & Bukkit logic
   -> helps keeping code up-to-date and similar (e.g. timestamp checks were disabled on bukkit but enabled on bungee)
   -> removes boilerplate code which is vulnerable for bugs
-> adds own and specific exceptions
-> improves/changes debug/log messages
-> i.g. code improvements / code style changes
-> some performance improvements
2020-05-12 23:59:46 +02:00
Steven Ferguson b8921033d1 Update version number 2020-05-12 03:59:21 -06:00
Steven Ferguson 92c27ed407 Update version number 2020-05-12 03:58:46 -06:00
RyanDeLap d4e41d2c28 Revert tinyprotocol changes, generic extra hostname info 2020-05-11 21:11:14 -05:00
Ryan De Lap 751bcafcc6 Merge pull request #4 from TCPShield/timestamp-valdiation
Update to block legacy ping
2020-04-28 08:50:56 -05:00
RyanDeLap a641acdfb6 Small cleanup 2020-04-28 08:50:45 -05:00
RyanDeLap c89705c0c8 Update to block legacy ping 2020-04-28 08:48:37 -05:00
65 changed files with 3319 additions and 617 deletions

No files matched your search

+15 -3
View File
@@ -1,4 +1,16 @@
# Testing
ip-whitelist/
# IntelliJ
.idea/
*/target/
*/*/target/
*.iml
build/
*.iml
# Gradle
.gradle/
gradle/
gradlew
gradlew.bat
libs/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 TCPShield
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+27 -6
View File
@@ -1,13 +1,34 @@
# RealIP
Bungee/Spigot plugin which parses client IP addresses passed from the TCPShield network.
# TCPShield
TCPShield is the plugin for the same named DDoS mitigation service [TCPShield](https://tcpshield.com).
This plugin is responsible for validating clients join via the TCPShield network.
It also parses passed IP addresses so the server is aware of the real player IP address.
### Compatibility
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
### Setup
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/panel/tcpshield-plugin) guidelines.
### Compiling
To compile this project, install maven and run the following command:
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
```
mvn clean install
gradle build
```
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
Please contact [@RyanDeLap](https://t.me/RyanDeLap) on Telegram for all support related to this product.
See [Contact](https://tcpshield.com/#contact)
Before contact, ensure you have tested with no Bungee Plugins installed. Plugins like BungeePluginManager will break this plugin!
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Dylan Keir](https://github.com/DylanKeir)
* [Paul Zhang](https://github.com/paulzhng)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+178
View File
@@ -0,0 +1,178 @@
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
// --
// Building
// --
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath(group: 'org.yaml', name: 'snakeyaml', version: '1.26')
classpath(group: 'com.google.code.gson', name: 'gson', version: '2.8.6')
}
}
plugins {
id 'java'
id 'idea'
}
// --
// Variables
// --
version = '2.7.0'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
// --
// Misc.
// --
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
compileJava {
sourceCompatibility = "8"
targetCompatibility = "8"
options.encoding = 'UTF-8'
}
// --
// Dependencies
// --
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
maven {
url = 'https://repo.dmulloy2.net/nexus/repository/public/'
}
maven {
url = 'https://oss.sonatype.org/content/repositories/snapshots'
}
maven {
url = 'https://papermc.io/repo/repository/maven-public/'
}
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = "https://repo.opencollab.dev/maven-snapshots/"
}
}
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '5.1.0'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.15.2-R0.1-SNAPSHOT'
// BungeeCord
compileOnly group: 'net.md-5', name: 'bungeecord-api', version: '1.14-SNAPSHOT'
// Velocity
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'
// Testing
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'
compileOnly 'org.geysermc.floodgate:api:2.1.1-SNAPSHOT'
}
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
// --
// Build Tasks
// --
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
task updateVersion {
updateYamls()
updateJsons()
}
void updateYamls() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
options.setPrettyFlow(true)
def yaml = new Yaml(options)
def bukkitYamlFile = new File('src/main/resources/plugin.yml')
def bungeeYamlFile = new File('src/main/resources/bungee.yml')
def files = [bukkitYamlFile, bungeeYamlFile]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
def cfg = yaml.load(inputStream)
cfg.put("version", version)
new FileWriter(file).withCloseable { writer ->
yaml.dump(cfg, writer)
}
}
}
}
void updateJsons() {
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
new InputStreamReader(inputStream, StandardCharsets.UTF_8).withCloseable { inputStreamReader ->
JsonObject object = JsonParser.parseReader(inputStreamReader).getAsJsonObject()
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(object.toString())
}
}
}
}
}
build.dependsOn updateVersion
-85
View File
@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tcpshield.realip</groupId>
<artifactId>RealIP</artifactId>
<version>1.4-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>RealIP</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>*.yml</include>
<include>*.key</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = 'TCPShield'
@@ -1,192 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Handshake.Client;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.StructureModifier;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class PacketHandler
extends PacketAdapter {
private String properField = null;
private final Logger logger;
private final boolean onlyProxy;
private final boolean debugMode;
public PacketHandler(Logger logger, boolean onlyProxy, boolean debugMode) {
super(TCPShieldBukkit.getInstance(), new PacketType[]{PacketType.Handshake.Client.SET_PROTOCOL});
this.logger = logger;
this.onlyProxy = onlyProxy;
this.debugMode = debugMode;
}
public void onPacketReceiving(PacketEvent event) {
boolean proxyConnection = false;
String raw = null;
try {
raw = (String) event.getPacket().getStrings().read(0);
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (signature.contains("%%%")) {
signature = signature.split("%%%", 2)[0];
}
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
Object injector = ReflectionUtils.getPrivateField(ignored.getClass(), ignored, "injector");
Object networkManager = ReflectionUtils.getPrivateField(injector.getClass(), injector, "networkManager");
if (this.properField == null) {
this.properField = ReflectionUtils.getProperField(networkManager.getClass());
}
Channel channel = (Channel) ReflectionUtils.getPrivateField(injector.getClass(), injector, "originalChannel");
InetSocketAddress newRemoteAddress = new InetSocketAddress(host, port);
proxyConnection = true;
try {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, this.properField == null ? "l" : this.properField, newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
ReflectionUtils.setFinalField(AbstractChannel.class, channel, "remoteAddress", newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
event.getPacket().getStrings().write(0, hostname);
}
}
} catch (Exception ex) {
Player player;
ex.printStackTrace();
} finally {
if ((this.onlyProxy) && (!proxyConnection)) {
Player player = event.getPlayer();
if(this.debugMode) {
this.logger.warning("Disconnecting " + player.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if(raw != null) {
this.logger.warning(raw);
}
player.kickPlayer("");
}
}
}
private Channel getChannel(Player player) {
try {
Class<? extends Player> playerClass = player.getClass();
Object playerHandle = playerClass.getMethod("getHandle", new Class[0]).invoke(player, new Object[0]);
Object playerConnection = getNmsClass("EntityPlayer").getField("playerConnection").get(playerHandle);
Object networkManager = getNmsClass("PlayerConnection").getField("networkManager").get(playerConnection);
return (Channel) getNmsClass("NetworkManager").getField("channel").get(networkManager);
} catch (Exception e) {
throw new RuntimeException("Couldn't get player channel", e);
}
}
private Class getNmsClass(String name)
throws ClassNotFoundException {
return Class.forName(getNmsPackage() + "." + name);
}
private String getNmsPackage() {
String obcString = Bukkit.getServer().getClass().getPackage().getName();
String versionString = obcString.substring(obcString.lastIndexOf('.') + 1);
return "net.minecraft.server." + versionString;
}
public static class ReflectionUtils {
private static final Field FIELD_MODIFIERS;
private static Map<Class<?>, Map<String, Field>> cachedFields = new HashMap();
public static void setFinalField(Class objectClass, Object object, String fieldName, Object value)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
FIELD_MODIFIERS.setInt(field, field.getModifiers() & 0xFFFFFFEF);
}
field.set(object, value);
}
public static Object getPrivateField(Class objectClass, Object object, String fieldName)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
return field.get(object);
}
public static String getProperField(Class objectClass) {
for (Field f : objectClass.getFields()) {
if (f.getType() == SocketAddress.class) {
return f.getName();
}
}
return "N/A";
}
private static Field getDeclaredField(Class<?> clazz, String fieldName) {
if (!cachedFields.containsKey(clazz)) {
cachedFields.put(clazz, new HashMap());
}
Map<String, Field> clazzCache = (Map) cachedFields.get(clazz);
if (clazzCache.containsKey(fieldName)) {
return (Field) clazzCache.get(fieldName);
}
try {
Field field = clazz.getDeclaredField(fieldName);
clazzCache.put(fieldName, field);
return field;
} catch (NoSuchFieldException e) {
clazzCache.put(fieldName, null);
throw new RuntimeException(e);
}
}
static {
Field field = null;
try {
field = getDeclaredField(Field.class, "modifiers");
field.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
FIELD_MODIFIERS = field;
}
}
}
@@ -1,36 +0,0 @@
package net.tcpshield.realip;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Signing {
private static PublicKey publicKey;
public static void init()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(Signing.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public static boolean verify(byte[] data, String signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
}
}
@@ -1,48 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Logger;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit
extends JavaPlugin {
private static TCPShieldBukkit INSTANCE;
private static Boolean stopping = Boolean.valueOf(false);
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
INSTANCE = this;
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
saveDefaultConfig();
FileConfiguration config = getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProtocolLibrary.getProtocolManager().addPacketListener(new PacketHandler(getLogger(), this.onlyProxy, this.debugMode));
}
public void onDisable() {
stopping = Boolean.valueOf(true);
INSTANCE = null;
}
public static TCPShieldBukkit getInstance() {
if (stopping.booleanValue()) {
throw new IllegalAccessError("Plugin is disabling!");
}
return INSTANCE;
}
}
@@ -1,239 +0,0 @@
package net.tcpshield.realip;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.event.EventHandler;
public class TCPShieldBungee
extends Plugin
implements Listener {
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
saveDefaultConfig();
Configuration config = loadConfig();
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProxyServer proxy = getProxy();
PluginManager pluginManager = proxy.getPluginManager();
pluginManager.registerListener(this, this);
Logger logger = ProxyServer.getInstance().getLogger();
Logger newLogger = new Logger("BungeeCord", null) {
public void log(Level level, String msg, Object param1) {
if ((msg.equals("{0} has connected")) && (param1.getClass().getSimpleName().equals("InitialHandler"))) {
return;
}
super.log(level, msg, param1);
}
};
newLogger.setParent(logger);
try {
set(ProxyServer.getInstance(), "logger", newLogger);
} catch (Exception e) {
e.printStackTrace();
}
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
private void saveDefaultConfig() {
File dataFolder = getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdir();
}
File file = getConfigFile();
if (!file.exists()) {
try {
InputStream in = getResourceAsStream("config.yml");
Throwable localThrowable3 = null;
try {
Files.copy(in, file.toPath(), new CopyOption[0]);
} catch (Throwable localThrowable1) {
localThrowable3 = localThrowable1;
throw localThrowable1;
} finally {
if (in != null) {
if (localThrowable3 != null) {
try {
in.close();
} catch (Throwable localThrowable2) {
localThrowable3.addSuppressed(localThrowable2);
}
} else {
in.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private File getConfigFile() {
File dataFolder = getDataFolder();
return new File(dataFolder, "config.yml");
}
@EventHandler(priority = -64)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
boolean proxyConnection = false;
Channel channel = null;
try {
Field wrapperField = e.getConnection().getClass().getDeclaredField("ch");
wrapperField.setAccessible(true);
Object wrapper = wrapperField.get(e.getConnection());
wrapperField.setAccessible(false);
Field chField = wrapper.getClass().getDeclaredField("ch");
chField.setAccessible(true);
Channel ch = (Channel) chField.get(wrapper);
chField.setAccessible(false);
channel = ch;
if (e.getHandshake().getHost().contains("//")) {
String raw = e.getHandshake().getHost();
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (signature.contains("%%%")) {
signature = signature.split("%%%", 2)[0];
}
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
long currentTime = System.currentTimeMillis() / 1000;
if(!(timestamp >= (currentTime - 2) && timestamp <= (currentTime + 2))) {
if(this.debugMode) {
getLogger().warning("Current time: " + currentTime + ", Timestamp Time: " + timestamp);
}
throw new Exception("Invalid signature timestamp, please check system's local clock if error persists.");
}
hostname = hostname.replace("%%%", "\0");
PendingConnection connection = e.getConnection();
InetSocketAddress sockadd = new InetSocketAddress(host, port);
proxyConnection = true;
try {
set(wrapper.getClass(), wrapper, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "localAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
InetSocketAddress virtualHost = new InetSocketAddress(hostname, e.getHandshake().getPort());
try {
set(connection, "virtualHost", virtualHost);
} catch (Exception ex) {
try {
set(connection, "vHost", virtualHost);
} catch (Exception e2) {
e2.printStackTrace();
}
}
try {
set(e.getHandshake(), "host", hostname);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
} catch (Exception localException) {
localException.printStackTrace();
} finally {
if (this.onlyProxy && !proxyConnection) {
Logger logger = getLogger();
PendingConnection connection = e.getConnection();
if(this.debugMode) {
logger.warning("Disconnecting " + connection.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if (channel != null) {
channel.flush().close();
} else {
connection.disconnect(new BaseComponent[0]);
}
}
}
}
protected static void set(Object main_object, String field, Object value)
throws Exception {
Field f = main_object.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
protected static void set(Class<?> c, Object main_object, String field, Object value)
throws Exception {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
}
@@ -0,0 +1,206 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.exception.parse.InvalidPayloadException;
import net.tcpshield.tcpshield.util.exception.parse.SignatureValidationException;
import net.tcpshield.tcpshield.util.exception.parse.TimestampValidationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InvalidSecretException;
import net.tcpshield.tcpshield.util.validation.SignatureValidator;
import net.tcpshield.tcpshield.util.validation.cidr.CIDRValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.impl.HTPDateTimestampValidator;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
/**
* Packet handler for TCPShield's plugins
*/
public class TCPShieldPacketHandler {
private final TCPShieldPlugin plugin;
private TimestampValidator timestampValidator;
private SignatureValidator signatureValidator;
private CIDRValidator cidrValidator;
/**
* Creates an instance of the TCPShieldPacketHandler
*
* @param plugin The TCPShield plugin
*/
public TCPShieldPacketHandler(TCPShieldPlugin plugin) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
this.plugin = plugin;
initValidators();
}
/**
* Initiates the validators
*
* @throws NoSuchAlgorithmException SignatureValidator exception
* @throws IOException SignatureValidator exception
* @throws InvalidKeySpecException SignatureValidator exception
*/
private void initValidators() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureValidator = new SignatureValidator();
switch (plugin.getConfigProvider().getTimestampValidationMode().toLowerCase()) {
case "system": {
timestampValidator = TimestampValidator.createDefault(plugin);
break;
}
case "htpdate": {
timestampValidator = new HTPDateTimestampValidator(plugin);
break;
}
case "off": {
timestampValidator = TimestampValidator.createEmpty(plugin);
break;
}
default: {
plugin.getDebugger().warn(
"Unknown timestamp validation mode \"%s\"! Disabling timestamp validation.",
plugin.getConfigProvider().getTimestampValidationMode());
timestampValidator = TimestampValidator.createEmpty(plugin);
break;
}
}
cidrValidator = new CIDRValidator(plugin);
}
/**
* Handles the packet and player manipulation
* for the initial handshake from TCPShield
* servers
*
* @param packet The handshake packet
* @param player The involved player
*/
public void handleHandshake(PacketProvider packet, PlayerProvider player) throws HandshakeException {
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
String extraData = null;
String[] payload = packet.getPayloadString().split("///");
if (payload.length != 4)
if (cidrValidator.validate(inetAddress))
return; // Allow connection with no processing
else
throw new InvalidPayloadException("length: " + payload.length + ", payload: " + Arrays.toString(payload) + ", raw payload: " + packet.getPayloadString());
int nullIndex;
if ((nullIndex = payload[3].indexOf('\0')) != -1) { // FML tagged payload
String originalData = payload[3];
payload[3] = originalData.substring(0, nullIndex);
extraData = originalData.substring(nullIndex);
}
String hostname = payload[0];
String ipData = payload[1];
int timestamp = Integer.parseInt(payload[2]);
String signature = payload[3];
String[] ipParts;
String host;
int port;
if (timestamp == 0 && GeyserUtils.GEYSER_SUPPORT_ENABLED) {
// Remap the altered layout
ipData = payload[0];
signature = payload[1];
hostname = payload[3];
// This is annoying having to have this in both blocks but w/e
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
if (!signature.equals(GeyserUtils.SESSION_SECRET)) {
throw new InvalidSecretException("Invalid secret: " + signature);
}
} else {
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!timestampValidator.validate(timestamp))
throw new TimestampValidationException(timestampValidator, timestamp);
if (!signatureValidator.validate(reconstructedPayload, signature))
throw new SignatureValidationException();
}
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.setPacketHostname(hostname);
} catch (NumberFormatException | InvalidPayloadException e) {
plugin.getDebugger().warn(
"%s[%s/%s] was disconnected because no proxy info was received and only-allow-proxy-connections is enabled. Raw payload = \"%s\"",
player.getName(), player.getUUID(), player.getIP(), packet.getPayloadString());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
if (!(e instanceof InvalidPayloadException))
throw new HandshakeException(new InvalidPayloadException(e));
else
throw new HandshakeException(e);
} catch (TimestampValidationException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided valid handshake information, but timestamp was not valid. " +
"Provided timestamp: %d vs. system timestamp: %d. Please check your machine time. Timestamp validation mode: %s",
player.getName(), player.getUUID(), player.getIP(), e.getTimestamp(), e.getTimestampValidator().getUnixTime(), plugin.getConfigProvider().getTimestampValidationMode());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (SignatureValidationException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"",
player.getName(), player.getUUID(), player.getIP(), packet.getPayloadString());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (UnknownHostException e) {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (InvalidSecretException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided an invalid session secret when authenticating a geyser connection.",
player.getName(), player.getUUID(), player.getIP());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (Exception e) {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
if (!(e instanceof HandshakeException))
throw new HandshakeException(e);
else
throw e;
}
}
}
@@ -0,0 +1,58 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import java.util.logging.Logger;
/**
* The base/provider for all entry points/main classes
*/
public interface TCPShieldPlugin {
/**
* Gets the plugin's config provider
*
* @return The plugin's config provider
*/
ConfigProvider getConfigProvider();
/**
* Gets the plugin's logger
*
* @return The plugin's logger
*/
Logger getLogger();
/**
* Gets the packet handler
*
* @return The packet handler
*/
TCPShieldPacketHandler getPacketHandler();
/**
* Gets the plugin's debugger
*
* @return The plugin's debugger
*/
Debugger getDebugger();
/**
* Default initialization of TCPShield, called after interface defaults are set
*/
default void initialization() {
String jvmVersion = System.getProperty("java.version");
try {
// Java 11 check/warning (Will eventually force Java 16 without warning)
String[] versionParts = jvmVersion.split("\\.");
int baseVersion = Integer.parseInt(versionParts[0]);
if (baseVersion < 11) // Java 8, and below, starts with 1, but since we are using Java 11 we can ignore sub values
this.getDebugger().warn("The Java version you are running is outdated for TCPShield and may cause issues. Update to atleast Java 11. Your version: Java %s", jvmVersion);
} catch (Throwable t) {
this.getDebugger().error("Failed to check java version for string: " + jvmVersion);
}
}
}
@@ -0,0 +1,102 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Bukkit's configuration implementation
*/
public class BukkitConfig extends ConfigProvider {
private final JavaPlugin plugin;
private FileConfiguration loadedConfiguration;
public BukkitConfig(JavaPlugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = plugin.getDataFolder();
this.configFile = new File(dataFolder, "config.yml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedConfiguration.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedConfiguration = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = plugin.getResource("config.yml")) { // Have to use Bukkit's resource streaming or the resource wont be found
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
loadedConfiguration = YamlConfiguration.loadConfiguration(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode", "enable-geyser-support", "prefer-protocollib");
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
this.preferProtocolLib = loadedConfiguration.getBoolean("prefer-protocollib");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(e);
}
}
}
@@ -0,0 +1,75 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bukkit.paper.BukkitPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.BukkitProtocolLib;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The entry point for Bukkit servers
*/
public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
private BukkitImplProvider bukkitImpl;
@Override
public void onEnable() {
try {
configProvider = new BukkitConfig(this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
// check force plib option -> paper -> plib -> error
if (this.configProvider.preferProtocolLib() && getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
bukkitImpl = new BukkitProtocolLib(this);
} else if (BukkitImplProvider.hasPaperEvent()) {
bukkitImpl = new BukkitPaper(this);
} else if (getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
bukkitImpl = new BukkitProtocolLib(this);
} else {
getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
bukkitImpl.load();
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception e) {
throw new InitializationException(e);
}
}
/*
* The provider's base methods
*/
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
}
@@ -0,0 +1,22 @@
package net.tcpshield.tcpshield.bukkit.paper;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
import net.tcpshield.tcpshield.bukkit.paper.handler.PaperHandshakeHandler;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
/**
* Bukkit provider for the PaperSpigot implementation of TCPShield
*/
public class BukkitPaper extends BukkitImplProvider {
public BukkitPaper(TCPShieldBukkit bukkitPlugin) {
super(bukkitPlugin);
}
@Override
public void load() {
PaperHandshakeHandler packetHandler = new PaperHandshakeHandler(this);
getPlugin().getServer().getPluginManager().registerEvents(packetHandler, getPlugin());
}
}
@@ -0,0 +1,42 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
/**
* The handshake handler for PaperSpigot
*/
public class PaperHandshakeHandler implements Listener {
private final BukkitImplProvider bukkitProvider;
public PaperHandshakeHandler(BukkitImplProvider bukkitProvider) {
this.bukkitProvider = bukkitProvider;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
PaperPacket packet = new PaperPacket(e);
PaperPlayer player = new PaperPlayer(e);
try {
bukkitProvider.getPlugin().getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
bukkitProvider.getPlugin().getDebugger().exception(exception);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy())
e.setCancelled(true);
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet wrapper for PaperSpigot
*/
public class PaperPacket implements PacketProvider {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPacket(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getPayloadString() {
return handshakeEvent.getOriginalHandshake();
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
handshakeEvent.setCancelled(false);
handshakeEvent.setServerHostname(hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,64 @@
package net.tcpshield.tcpshield.bukkit.paper.handler;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import java.net.InetSocketAddress;
import java.util.UUID;
/**
* A player wrapper for PaperSpigot
*/
public class PaperPlayer implements PlayerProvider {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayer(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
/**
* Trys to grab the UUID of the handshake
* @return If found, the corrosponding uuid, if not, unknown
*/
@Override
public String getUUID() {
UUID uuid = handshakeEvent.getUniqueId();
if (uuid == null)
return "unknown";
return uuid.toString();
}
/**
* Unsupported with Paper handshakes
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return handshakeEvent.getSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false); // Caused issues with newer versions of Paper (Thanks https://github.com/realDragonium)
handshakeEvent.setFailMessage("Connection failed. Please try again or contact an administrator.");
handshakeEvent.setFailed(true);
}
}
@@ -0,0 +1,22 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
import net.tcpshield.tcpshield.bukkit.protocollib.handler.ProtocolLibHandshakeHandler;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
/**
* Bukkit provider for the ProtocolLib implementation of TCPShield
*/
public class BukkitProtocolLib extends BukkitImplProvider {
public BukkitProtocolLib(TCPShieldBukkit bukkitPlugin) {
super(bukkitPlugin);
}
@Override
public void load() {
ProtocolLibHandshakeHandler packetHandler = new ProtocolLibHandshakeHandler(this);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
}
}
@@ -0,0 +1,34 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for ProtocolLib
*/
public class ProtocolLibHandshakeHandler extends PacketAdapter {
private final BukkitImplProvider bukkitProvider;
public ProtocolLibHandshakeHandler(BukkitImplProvider bukkitProvider) {
super(bukkitProvider.getPlugin(), ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.bukkitProvider = bukkitProvider;
}
@Override
public void onPacketReceiving(PacketEvent e) {
ProtocolLibPacket packet = new ProtocolLibPacket(e.getPacket());
ProtocolLibPlayer player = new ProtocolLibPlayer(e.getPlayer());
try {
bukkitProvider.getPlugin().getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
bukkitProvider.getPlugin().getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,34 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.events.PacketContainer;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet wrapper for ProtocolLib
*/
public class ProtocolLibPacket implements PacketProvider {
private final PacketContainer packetContainer;
private final String rawPayload;
public ProtocolLibPacket(PacketContainer packetContainer) {
this.packetContainer = packetContainer;
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getPayloadString() {
return rawPayload;
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
packetContainer.getStrings().write(0, hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,92 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
/**
* A player wrapper for ProtocolLib
*/
public class ProtocolLibPlayer implements PlayerProvider {
/*
* Reflection objects needed for manipulation
*/
private static Class<?> abstractChannelClass;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new InitializationException(new ReflectionException(e2));
}
}
}
private final Player player;
private String ip;
public ProtocolLibPlayer(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
/**
* Unsupported with ProtocolLib handshakes
* @return unknown
*/
@Override
public String getUUID() {
return "unknown";
}
/**
* Unsupported with ProtocolLib handshakes
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
MinimalInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtil.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtil.getObjectInPrivateField(injector, "networkManager");
ReflectionUtil.setFinalField(networkManager, ReflectionUtil.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtil.getObjectInPrivateField(injector, "wrappedChannel");
ReflectionUtil.setFinalField(channel, ReflectionUtil.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -0,0 +1,40 @@
package net.tcpshield.tcpshield.bukkit.provider;
import net.tcpshield.tcpshield.bukkit.TCPShieldBukkit;
/**
* A provider for Bukkit implementations of TCPShield
*/
public abstract class BukkitImplProvider {
private final TCPShieldBukkit bukkitPlugin;
public BukkitImplProvider(TCPShieldBukkit bukkitPlugin) {
this.bukkitPlugin = bukkitPlugin;
}
public TCPShieldBukkit getPlugin() {
return bukkitPlugin;
}
/**
* Loads the Bukkit provider
*/
public abstract void load();
/**
* Checks for the Paper handshake event class for the Paper implementation
* @return Boolean stating if the runtime has the proper Paper event
*/
public static boolean hasPaperEvent() {
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,105 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import net.tcpshield.tcpshield.velocity.VelocityConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Bungee's configuration implementation
*/
public class BungeeConfig extends ConfigProvider {
private final Plugin plugin;
private Configuration loadedConfiguration;
public BungeeConfig(Plugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = plugin.getDataFolder();
this.configFile = new File(dataFolder, "config.yml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedConfiguration.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedConfiguration = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
loadedConfiguration = configProvider.load(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(e);
}
}
}
@@ -0,0 +1,60 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bungee.handler.BungeeHandshakeHandler;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
/**
* The entry point for Bungee servers
*/
public class TCPShieldBungee extends Plugin implements TCPShieldPlugin, Listener {
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Override
public void onEnable() {
try {
configProvider = new BungeeConfig(this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakeHandler(this));
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception e) {
throw new InitializationException(e);
}
}
/*
* The provider's base methods
*/
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
}
@@ -0,0 +1,47 @@
package net.tcpshield.tcpshield.bungee.handler;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for Bungee
*/
public class BungeeHandshakeHandler implements Listener {
private final TCPShieldPlugin plugin;
public BungeeHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy())
return;
connection.disconnect();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
BungeePacket packet = new BungeePacket(e.getHandshake(), e.getConnection());
BungeePlayer player = new BungeePlayer(e.getConnection());
this.plugin.getDebugger().warn("BungeeCord: Raw player hostname: " + packet.getPayloadString());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
plugin.getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,46 @@
package net.tcpshield.tcpshield.bungee.handler;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.packet.Handshake;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
import java.net.InetSocketAddress;
/**
* A packet wrapper for Bungee
*/
public class BungeePacket implements PacketProvider {
private final Handshake handshake;
private final PendingConnection pendingConnection;
public BungeePacket(Handshake handshake, PendingConnection pendingConnection) {
this.handshake = handshake;
this.pendingConnection = pendingConnection;
}
@Override
public String getPayloadString() {
return handshake.getHost();
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
InetSocketAddress virtualHost = InetSocketAddress.createUnresolved(hostname, handshake.getPort());
try {
ReflectionUtil.setFinalField(pendingConnection, "virtualHost", virtualHost);
} catch (Exception ex) {
ReflectionUtil.setFinalField(pendingConnection, "vHost", virtualHost);
}
ReflectionUtil.setField(handshake, "host", hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
}
@@ -0,0 +1,82 @@
package net.tcpshield.tcpshield.bungee.handler;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
/**
* A player wrapper for Bungee
*/
public class BungeePlayer implements PlayerProvider {
private final PendingConnection pendingConnection;
private String ip;
public BungeePlayer(PendingConnection pendingConnection) {
this.pendingConnection = pendingConnection;
this.ip = pendingConnection.getAddress().getAddress().getHostAddress();
}
/**
* Trys to grab the UUID of the handshake
*
* @return If found, the corrosponding uuid, if not, unknown
*/
@Override
public String getUUID() {
UUID uuid = pendingConnection.getUniqueId();
if (uuid == null)
return "unknown";
return uuid.toString();
}
@Override
public String getName() {
return pendingConnection.getName();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object channelWrapper = ReflectionUtil.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtil.getObjectInPrivateField(channelWrapper, "ch");
try {
Field socketAddressField = ReflectionUtil.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtil.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(channel.getClass(), "remoteAddress"), ip);
try {
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(channel.getClass(), "localAddress"), ip);
} catch (Throwable t) {
// ChannelWrapper doesn't have a localAddress
}
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
@Override
public void disconnect() {
pendingConnection.disconnect();
}
}
@@ -0,0 +1,49 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import org.geysermc.floodgate.api.InstanceHolder;
import java.util.Arrays;
public class GeyserHandshakeHandler {
private final TCPShieldPlugin plugin;
public GeyserHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
public void init() {
InstanceHolder.getHandshakeHandlers().addHandshakeHandler(data -> {
if (data.getIp() == null) {
// not bedrock
this.plugin.getDebugger().warn("Connection with no bedrock data joined and ignored: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
return;
}
this.plugin.getDebugger().warn("Bedrock connection joined and validated: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
String oldPayload = data.getHostname();
if (oldPayload.contains("///")) {
oldPayload = oldPayload.split("///")[0];
}
String hostname = oldPayload;
String realIp = data.getIp();
String timestamp = "0";
String signature = GeyserUtils.SESSION_SECRET;
// so floodgate likes to append "\0Floodgate\0" or something stupid to the hostname
// which just completely breaks this, so we have to pass these in a different
// order to stop bungeecord detecting it as FML data, thanks floodgate.
String newHostname = realIp + ":0///" + signature + "///" + timestamp + "///" + hostname;
if (hostname.contains("\0")) { // this is usually fine because FML, providing the hostname is first
this.plugin.getDebugger().warn("Hostname contains null byte: " + Arrays.toString(hostname.toCharArray()));
}
this.plugin.getDebugger().warn("Setting hostname to %s - %s", newHostname, Arrays.toString(newHostname.toCharArray()));
data.setHostname(newHostname);
});
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.util.UUID;
public class GeyserUtils {
public static final String SESSION_SECRET = UUID.randomUUID().toString();
public static boolean GEYSER_SUPPORT_ENABLED = false;
public static void initGeyser(TCPShieldPlugin plugin, ConfigProvider config) {
if (!(GEYSER_SUPPORT_ENABLED = useGeyser(config))) {
return;
}
GeyserHandshakeHandler geyserHandler = new GeyserHandshakeHandler(plugin);
geyserHandler.init();
}
private static boolean useGeyser(ConfigProvider config) {
if (!config.isGeyser()) return false;
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,101 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import java.io.File;
/**
* An abstract provider for TCPShield's options.
*/
public abstract class ConfigProvider {
/*
* Configuration options
*/
protected boolean onlyProxy = true;
protected String timestampValidationMode = "htpdate";
protected boolean doDebug = true; // Fail-safe default set to true
protected boolean geyser = false;
protected boolean velocityHandlePreLoginEvent = true;
// spigot/paper only option
protected boolean preferProtocolLib;
protected File dataFolder;
protected File configFile;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public boolean isGeyser() {
return this.geyser;
}
public boolean handlePreLoginEvent() {
return this.velocityHandlePreLoginEvent;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
public boolean doDebug() {
return this.doDebug;
}
public boolean preferProtocolLib() {
return this.preferProtocolLib;
}
public File getDataFolder() {
return dataFolder;
}
public File getConfigFile() {
return configFile;
}
/*
* Plugin Constants
*/
protected final long maxTimestampDifference = 3; // In Unix Timesteps (Seconds)
public long getMaxTimestampDifference() {
return maxTimestampDifference;
}
/*
* Required methods
*/
/**
* Deletes the current config saved to the disk and reinstalls the default config
* @throws ConfigResetException Thrown if resetting fails
*/
protected abstract void reset() throws ConfigResetException;
/**
* Trys to load the options from the config, if failed, throws ConfigLoadException
* @throws ConfigLoadException Thrown if loading fails, reset should be called if thrown
*/
protected abstract void load() throws ConfigLoadException;
/**
* Trys to reload the config, if failed, throws ConfigReloadException
* @throws ConfigReloadException Thrown if reloading fails
*/
public abstract void reload() throws ConfigReloadException;
/**
* Checks the provided nodes to see if they exist in the config
* @param nodes The nodes to check
* @throws ConfigException Thrown when a node isnt found
*/
protected abstract void checkNodes(String... nodes) throws ConfigException;
}
@@ -0,0 +1,22 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
/**
* A packet provider for TCPShield handling
*/
public interface PacketProvider {
/**
* Gets the raw payload from the packet
* @return The raw payload in the form of a String
*/
String getPayloadString();
/**
* Sets the hostname within the packet
* @param hostname The new hostname
*/
void setPacketHostname(String hostname) throws PacketManipulationException;
}
@@ -0,0 +1,27 @@
package net.tcpshield.tcpshield.provider;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import java.net.InetSocketAddress;
/**
* A player provider for TCPShield handling
*/
public interface PlayerProvider {
String getUUID();
String getName();
String getIP();
/**
* Sets the players IP address directly to the server
*
* @param ip The new IP address to pass to the proxy/server
*/
void setIP(InetSocketAddress ip) throws PlayerManipulationException;
void disconnect();
}
@@ -0,0 +1,114 @@
package net.tcpshield.tcpshield.util;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import java.util.logging.Logger;
/**
* A util for debugging
*/
public abstract class Debugger {
/**
* Creates a debugger instance according to
* the plugin's configuration options
* @param plugin The TCPShield plugin
* @return A debugger instance from plugin's configuration
*/
public static Debugger createDebugger(TCPShieldPlugin plugin) {
if (plugin.getConfigProvider().doDebug())
/*
* A non-empty debugger using the plugin's logger
*/
return new Debugger(plugin.getLogger()) {
@Override
public void info(String format, Object... formats) {
this.logger.info("Debug : " + String.format(format, formats));
}
@Override
public void warn(String format, Object... formats) {
this.logger.warning("Debug : " + String.format(format, formats));
}
@Override
public void error(String format, Object... formats) {
this.logger.severe("Debug : " + String.format(format, formats));
}
@Override
public void exception(Exception exception) {
exception.printStackTrace();
}
};
else
/*
* An empty debugger
*/
return new Debugger(null) {
@Override
public void info(String format, Object... formats) {
}
@Override
public void warn(String format, Object... formats) {
}
@Override
public void error(String format, Object... formats) {
}
@Override
public void exception(Exception exception) {
}
};
}
protected final Logger logger;
/**
* Non-accessable constructor for creating a debugger
* @param logger The plugin's logger
*/
private Debugger(Logger logger) {
this.logger = logger;
}
/**
* Outputs debug information with log level "INFO"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void info(String format, Object... formats);
/**
* Outputs debug information with log level "WARNING"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void warn(String format, Object... formats);
/**
* Outputs debug information with log level "SEVERE"
* @param format The output string to be formatted
* @param formats The formarts for the string
*/
public abstract void error(String format, Object... formats);
/**
* Outputs the exception through the debugger
* @param exception The exception to be outputted
*/
public abstract void exception(Exception exception);
}
@@ -0,0 +1,182 @@
package net.tcpshield.tcpshield.util;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
/**
* A util for reflection and manipulation
*/
public class ReflectionUtil {
/**
* Cached information for Fields
*/
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
// private static Field modifiersField;
/**
* Sets a final field, with a field name, inside an object
* @param object The object
* @param fieldName The field name
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setFinalField(Object object, String fieldName, Object value) throws ReflectionException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
/**
* Sets a final field, with a field object, inside an object
* @param object The object
* @param field The field
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setFinalField(Object object, Field field, Object value) throws ReflectionException {
field.setAccessible(true);
// Removed due to complications with Velocity (Thanks https://github.com/AoElite)
// if (Modifier.isFinal(field.getModifiers())) {
// if (modifiersField == null) {
// try {
// modifiersField = getDeclaredField(Field.class, "modifiers");
// } catch (ReflectionException e) { // workaround for when searching for the modifiers field on Java 12 or higher
// try {
// Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
// getDeclaredFields0.setAccessible(true);
//
// Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
// modifiersField = Arrays.stream(fields).filter(modifier -> modifier.getName().equals("modifiers")).findFirst().orElseThrow(() -> new ReflectionException("Could not find the modifiers field"));
// } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
// throw new ReflectionException(e2);
// }
// }
//
// modifiersField.setAccessible(true);
// }
//
// try {
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// } catch (IllegalAccessException e) {
// throw new ReflectionException(e);
// }
// }
setField(object, field, value);
}
/**
* Sets a field, with a field name, inside an object
* @param object The object
* @param fieldName The field name
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setField(Object object, String fieldName, Object value) throws ReflectionException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
/**
* Sets a field, with a field object, inside an object
* @param object The object
* @param field The field
* @param value The new value
* @throws ReflectionException Thrown when the operation was not successful
*/
public static void setField(Object object, Field field, Object value) throws ReflectionException {
try {
field.set(object, value);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
/**
* Gets the object inside a private field, with a field name, in an object
* @param object The object
* @param fieldName The field name
* @return The grabbed object
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Object getObjectInPrivateField(Object object, String fieldName) throws ReflectionException {
Field field = getPrivateField(object.getClass(), fieldName);
try {
return field.get(object);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
/**
* Gets a private field, with a field name, inside an object
* @param clazz The clazz
* @param fieldName The field name
* @return The grabbed field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field getPrivateField(Class<?> clazz, String fieldName) throws ReflectionException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
/**
* Searches for a field, with the type of the provided class, inside a class
* @param clazz The class to search through
* @param searchFor The class type to search for
* @return The found field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) throws ReflectionException {
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new ReflectionException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
/**
* Gets a declared field, with a field name, inside a class
* @param clazz The clazz
* @param fieldName The field name
* @return The declared field
* @throws ReflectionException Thrown when the operation was not successful
*/
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws ReflectionException {
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw new ReflectionException(e);
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,27 @@
package net.tcpshield.tcpshield.util.exception;
/**
* Outline for TCPShield exceptions
*/
public abstract class TCPShieldException extends RuntimeException {
public TCPShieldException(Throwable throwable) {
super(throwable);
}
public TCPShieldException(String message) {
super(message);
}
public TCPShieldException(String message, Throwable throwable) {
super(message, throwable);
}
public TCPShieldException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the loading phase of the config process
*/
public class ConfigLoadException extends ConfigException {
public ConfigLoadException(Throwable throwable) {
super(throwable);
}
public ConfigLoadException(String message) {
super(message);
}
public ConfigLoadException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigLoadException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the reloading phase of the config process
*/
public class ConfigReloadException extends ConfigException {
public ConfigReloadException(Throwable throwable) {
super(throwable);
}
public ConfigReloadException(String message) {
super(message);
}
public ConfigReloadException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigReloadException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.config;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
/**
* An exception thrown during the resetting phase of the config process
*/
public class ConfigResetException extends ConfigException {
public ConfigResetException(Throwable throwable) {
super(throwable);
}
public ConfigResetException(String message) {
super(message);
}
public ConfigResetException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigResetException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.manipulate;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown during packet manipulation
*/
public class PacketManipulationException extends HandshakeException {
public PacketManipulationException(Throwable throwable) {
super(throwable);
}
public PacketManipulationException(String message) {
super(message);
}
public PacketManipulationException(String message, Throwable throwable) {
super(message, throwable);
}
public PacketManipulationException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.manipulate;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown during player manipulation
*/
public class PlayerManipulationException extends HandshakeException {
public PlayerManipulationException(Throwable throwable) {
super(throwable);
}
public PlayerManipulationException(String message) {
super(message);
}
public PlayerManipulationException(String message, Throwable throwable) {
super(message, throwable);
}
public PlayerManipulationException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown when a handshake packet has invalid payload data
*/
public class InvalidPayloadException extends HandshakeException {
public InvalidPayloadException(Throwable throwable) {
super(throwable);
}
public InvalidPayloadException(String message) {
super(message);
}
public InvalidPayloadException(String message, Throwable throwable) {
super(message, throwable);
}
public InvalidPayloadException() {
super();
}
}
@@ -0,0 +1,14 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* An exception thrown when a handshake packet failed signature validation
*/
public class SignatureValidationException extends HandshakeException {
public SignatureValidationException() {
super();
}
}
@@ -0,0 +1,30 @@
package net.tcpshield.tcpshield.util.exception.parse;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
/**
* An exception thrown when a handshake packet failed timestamp validation
*/
public class TimestampValidationException extends HandshakeException {
private final TimestampValidator timestampValidator;
private final long timestamp;
public TimestampValidationException(TimestampValidator timestampValidator, long timestamp) {
super();
this.timestampValidator = timestampValidator;
this.timestamp = timestamp;
}
public TimestampValidator getTimestampValidator() {
return timestampValidator;
}
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the CIDR process of TCPShield
*/
public class CIDRException extends TCPShieldException {
public CIDRException(Throwable throwable) {
super("An exception occured during the CIDR process", throwable);
}
public CIDRException(String message) {
super(message);
}
public CIDRException(String message, Throwable throwable) {
super(message, throwable);
}
public CIDRException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the config loading, reloeading, saving, etc. process of TCPShield
*/
public class ConfigException extends TCPShieldException {
public ConfigException(Throwable throwable) {
super("An exception occured during the config process", throwable);
}
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable throwable) {
super(message, throwable);
}
public ConfigException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the handshake handling of TCPShield
*/
public class HandshakeException extends TCPShieldException {
public HandshakeException(Throwable throwable) {
super("An exception occured during the handshake process", throwable);
}
public HandshakeException(String message) {
super(message);
}
public HandshakeException(String message, Throwable throwable) {
super(message, throwable);
}
public HandshakeException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during the initialization phase of TCPShield
*/
public class InitializationException extends TCPShieldException {
public InitializationException(Throwable throwable) {
super("An exception occured during the initalization process", throwable);
}
public InitializationException(String message) {
super(message);
}
public InitializationException(String message, Throwable throwable) {
super(message, throwable);
}
public InitializationException() {
super();
}
}
@@ -0,0 +1,26 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
public class InvalidSecretException extends TCPShieldException {
public InvalidSecretException(Throwable throwable) {
super("An invalid secret was provided during the geyser handshake process", throwable);
}
public InvalidSecretException(String message) {
super(message);
}
public InvalidSecretException(String message, Throwable throwable) {
super(message, throwable);
}
public InvalidSecretException() {
super();
}
}
@@ -0,0 +1,29 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
/**
* An exception thrown during reflection operations for TCPShield
*/
public class ReflectionException extends TCPShieldException {
public ReflectionException(Throwable throwable) {
super("An exception occured during the reflection process", throwable);
}
public ReflectionException(String message) {
super(message);
}
public ReflectionException(String message, Throwable throwable) {
super(message, throwable);
}
public ReflectionException() {
super();
}
}
@@ -0,0 +1,58 @@
package net.tcpshield.tcpshield.util.validation;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* A signature validator using the TCPShield public key
*/
public class SignatureValidator {
private final PublicKey publicKey;
public SignatureValidator() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(SignatureValidator.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
/**
* Validates a String and Signature pair
* @param str The data in the form of a string
* @param signature The provided signature
* @return Boolean stating if it's a valid signature
*/
public boolean validate(String str, String signature) {
return validate(str.getBytes(StandardCharsets.UTF_8), signature);
}
/**
* Validates a byte[] and Signature pair
* @param data The data in the form of a byte array
* @param signature The provided signature
* @return Boolean stating if it's a valid signature
*/
private boolean validate(byte[] data, String signature) {
try {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
} catch (IllegalArgumentException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) {
return false;
}
}
}
@@ -0,0 +1,83 @@
package net.tcpshield.tcpshield.util.validation.cidr;
import net.tcpshield.tcpshield.util.exception.phase.CIDRException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* CIDR matcher for the CIDR validator
*/
public abstract class CIDRMatcher {
/**
* Creates a CIDRMatcher from a CIDR matching regex
* @param cidrMatchString The string containing the CIDR matching regex
* @return The corrosponding CIDRMatcher
*/
public static CIDRMatcher create(String cidrMatchString) {
return new CIDRMatcher(cidrMatchString) {
@Override
public boolean match(InetAddress inetAddress) {
return super.match(inetAddress);
}
};
}
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
private CIDRMatcher(String cidrMatchString) {
String[] split = cidrMatchString.split("/");
String parsedIPAddress;
if (split.length != 0) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = cidrMatchString;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new CIDRException(e);
}
}
/**
* Compares the provided InetAddress with the CIDR for a match
* @param inetAddress The InetAddress to match with the CIDR
* @return Boolean stating if the provided InetAddress matches the CIDR
*/
public boolean match(InetAddress inetAddress) {
if (!cidrAddress.getClass().equals(inetAddress.getClass())) return false; // check if IP is IPv4 or IPv6
if (simpleCIDR) return inetAddress.equals(cidrAddress); // check for equality if it's a simple CIDR
byte[] inetAddressBytes = inetAddress.getAddress();
byte[] requiredAddressBytes = cidrAddress.getAddress();
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
for (int i = 0; i < maskBytes; i++) {
if (inetAddressBytes[i] != requiredAddressBytes[i]) return false;
}
if (finalByte != 0)
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
return true;
}
}
@@ -0,0 +1,91 @@
package net.tcpshield.tcpshield.util.validation.cidr;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.CIDRException;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.util.*;
/**
* A CIDR validator for TCPShield
*/
public class CIDRValidator {
private final TCPShieldPlugin plugin;
private final File ipWhitelistFolder;
private final List<CIDRMatcher> cidrMatchers;
private final Set<String> cache = new HashSet<>(); // Max potential size is equilivent to the amount of whitelisted IP's
public CIDRValidator(TCPShieldPlugin plugin) throws CIDRException {
this.plugin = plugin;
ipWhitelistFolder = new File(plugin.getConfigProvider().getDataFolder(), "ip-whitelist");
if (!ipWhitelistFolder.exists())
ipWhitelistFolder.mkdir();
try {
List<String> whitelists = loadWhitelists();
cidrMatchers = loadCIDRMatchers(whitelists);
} catch (Exception e) {
throw new CIDRException(e);
}
}
private List<CIDRMatcher> loadCIDRMatchers(List<String> whitelists) {
List<CIDRMatcher> matchers = new ArrayList<>();
for (String whitelist : whitelists)
try {
matchers.add(CIDRMatcher.create(whitelist));
} catch (Exception e) {
plugin.getDebugger().warn("Exception occured while creating CIDRMatcher for \"%s\". Ignoring it.", whitelist);
plugin.getDebugger().exception(e);
}
return matchers;
}
private List<String> loadWhitelists() throws FileNotFoundException {
List<String> whitelists = new ArrayList<>();
for (File file : Objects.requireNonNull(ipWhitelistFolder.listFiles())) {
if (file.isDirectory())
continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
whitelists.add(cidrEntry);
}
}
}
return whitelists;
}
/**
* Validates an InetAddress with CIDR matchers
* @param inetAddress The InetAddress to validate
* @return Boolean stating if the InetAddress is validated with CIDR
*/
public boolean validate(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip))
return true;
for (CIDRMatcher cidrMatcher : cidrMatchers)
if (cidrMatcher.match(inetAddress)) {
cache.add(ip);
return true;
}
return false;
}
}
@@ -0,0 +1,61 @@
package net.tcpshield.tcpshield.util.validation.timestamp;
import net.tcpshield.tcpshield.TCPShieldPlugin;
/**
* Base for timestamp validators
*/
public abstract class TimestampValidator {
/**
* Creates an empty validator that always returns true
* @return An empty validator always returning true
*/
public static TimestampValidator createEmpty(TCPShieldPlugin plugin) {
return new TimestampValidator(plugin) {
@Override
public boolean validate(long timestamp) {
return true;
}
};
}
/**
* Creates a system validator that uses default validation
* @return A system validator
*/
public static TimestampValidator createDefault(TCPShieldPlugin plugin) {
return new TimestampValidator(plugin) {
// Uses default methods
};
}
protected final TCPShieldPlugin plugin;
public TimestampValidator(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
/**
* Validates given timestamp to see if it's within a reasonable
* time range
* @param timestamp The timestamp
* @return Boolean stating if its valid
*/
public boolean validate(long timestamp) {
return Math.abs(getUnixTime() - timestamp) <= plugin.getConfigProvider().getMaxTimestampDifference();
}
/**
* Returns the validator's current Unix time
* @return The current time in Unix format
* @default Returns the system time in Unix format
*/
public long getUnixTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -0,0 +1,80 @@
package net.tcpshield.tcpshield.util.validation.timestamp.impl;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/**
* A timestamp validator that offsets system time with Google's to sync
* tiemstamps
*/
public class HTPDateTimestampValidator extends TimestampValidator {
private volatile long htpDateOffset = 0;
public HTPDateTimestampValidator(TCPShieldPlugin plugin) {
super(plugin);
ForkJoinPool.commonPool().execute(() -> {
try {
updateHTPDateOffset();
} catch (Exception e) {
throw new InitializationException(e);
}
});
}
private void updateHTPDateOffset() throws IOException {
Socket socket = new Socket("google.com", 80);
String payload = "HEAD http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: tcpshield/1.0\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n";
socket.getOutputStream().write(payload.getBytes());
long readTime = System.currentTimeMillis(); // assuming server -> client time is negligible
List<String> response = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toList());
Date serverDate = parseDate(response);
htpDateOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
private Date parseDate(List<String> response) {
for (String line : response) {
if (!line.startsWith("Date: "))
continue;
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
try {
return sdf.parse(line.substring("Date: ".length()));
} catch (ParseException e) {
throw new IllegalStateException(e);
}
}
throw new IllegalArgumentException("no date line found - response: " + response);
}
@Override
public long getUnixTime() {
return (System.currentTimeMillis() + htpDateOffset) / 1000;
}
}
@@ -0,0 +1,86 @@
package net.tcpshield.tcpshield.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.velocity.handler.VelocityHandshakeHandler;
import java.nio.file.Path;
import java.util.logging.Logger;
/**
* The entry point for Velocity servers
*/
@Plugin(
id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity implements TCPShieldPlugin {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
try {
configProvider = new VelocityConfig(dataFolder.toFile(), this);
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
server.getEventManager().register(this, new VelocityHandshakeHandler(this));
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception exception) {
throw new InitializationException(exception);
}
}
/*
* The provider's base methods
*/
@Override
public ConfigProvider getConfigProvider() {
return configProvider;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public TCPShieldPacketHandler getPacketHandler() {
return packetHandler;
}
@Override
public Debugger getDebugger() {
return debugger;
}
}
@@ -0,0 +1,101 @@
package net.tcpshield.tcpshield.velocity;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.exception.config.ConfigLoadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigReloadException;
import net.tcpshield.tcpshield.util.exception.config.ConfigResetException;
import net.tcpshield.tcpshield.util.exception.phase.ConfigException;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
/**
* Velocity's configuration implementation
*/
public class VelocityConfig extends ConfigProvider {
private final TCPShieldPlugin plugin;
private Toml loadedToml;
public VelocityConfig(File dataFolder, TCPShieldPlugin plugin) throws ConfigException {
this.plugin = plugin;
this.dataFolder = dataFolder;
this.configFile = new File(this.dataFolder, "config.toml");
try {
reload();
} catch (Exception e) {
throw new ConfigException(e);
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
if (!loadedToml.contains(node))
throw new ConfigException("The node \"" + node + "\" does not exist in the config.");
}
}
@Override
protected void reset() throws ConfigResetException {
try {
loadedToml = null; // To the garbage collector you go
try {
configFile.delete();
} catch (Exception ignored) {
// Just ignore since it either does not exist, or we can overwrite
}
try (InputStream in = VelocityConfig.class.getResourceAsStream("/config.toml")) {
Files.copy(in, configFile.toPath());
}
} catch (Exception e) {
throw new ConfigResetException(e);
}
}
@Override
protected void load() throws ConfigLoadException {
try {
loadedToml = new Toml().read(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode", "enable-geyser-support", "pre-login-event");
this.onlyProxy = loadedToml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedToml.getString("timestamp-validation");
this.doDebug = loadedToml.getBoolean("debug-mode");
this.geyser = loadedToml.getBoolean("enable-geyser-support");
this.velocityHandlePreLoginEvent = loadedToml.getBoolean("pre-login-event");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
}
@Override
public void reload() throws ConfigReloadException {
try {
if (!dataFolder.exists())
dataFolder.mkdir();
if (!configFile.exists())
reset();
try {
load();
} catch (ConfigLoadException exception) {
plugin.getLogger().warning("Config loading failed, resetting to default config. (This can be ignored if you just switched builds of TCPShield)");
reset();
reload(); // Redo cycle, possible StackOverFlow, but realistically only happens if reset fails
}
} catch (Exception e) {
throw new ConfigReloadException(e);
}
}
}
@@ -0,0 +1,83 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
/**
* The handshake handler for Velocity
*/
public class VelocityHandshakeHandler {
private final TCPShieldPlugin plugin;
private static Class<?> CONNECTED_PLAYER_CONNECTION_CLASS;
static {
try {
CONNECTED_PLAYER_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.ConnectedPlayer");
} catch (Exception e) {
// ignore for old velocity versions
}
}
public VelocityHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
// Turns out this event sometimes passes erroneous hostnames
// which have the null bytes terminated as FML data which causes
// issues with the verification process.
@Subscribe(order = PostOrder.FIRST)
public void onPreLogin(PreLoginEvent e) {
if (!this.plugin.getConfigProvider().handlePreLoginEvent()) {
return;
}
InboundConnection connection = e.getConnection();
handleEvent(connection, "onPreLogin");
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection, "onHandshake");
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
if (connection.getClass() == CONNECTED_PLAYER_CONNECTION_CLASS) {
// new ServerData (0x42) packet on connect, we don't care about it
return;
}
handleEvent(connection, "onProxyPing");
}
private void handleEvent(InboundConnection connection, String debugSource) {
VelocityPlayer player = new VelocityPlayer(connection);
if (player.getConnectionType() == VelocityPlayer.ConnectionType.LEGACY) {
player.disconnect();
return;
}
VelocityPacket packet = new VelocityPacket(connection);
this.plugin.getDebugger().warn("Velocity: " + debugSource + " Raw player hostname: " + packet.getPayloadString());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
plugin.getDebugger().exception(exception);
}
}
}
@@ -0,0 +1,117 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PacketManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
/**
* A packet wrapper for Velocity
*/
public class VelocityPacket implements PacketProvider {
/*
* Reflection objects needed for manipulation
*/
private static final Field HANDSHAKE_FIELD;
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "handshake");
Field hostnameField;
try {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.HandshakePacket"), "serverAddress");
} catch (Exception e) {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
}
HOSTNAME_FIELD = hostnameField;
CLEANED_ADDRESS_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "cleanedAddress");
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacket(InboundConnection inboundConnection) {
// support new velocity connection type
if (inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
try {
inboundConnection = (InboundConnection) LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(inboundConnection);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
} catch (IllegalAccessException e) {
throw new PacketManipulationException(e);
}
}
@Override
public String getPayloadString() {
return rawPayload;
}
@Override
public void setPacketHostname(String hostname) throws PacketManipulationException {
try {
ReflectionUtil.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
} catch (Exception e) {
throw new PacketManipulationException(e);
}
}
/**
* Adapted from https://github.com/VelocityPowered/Velocity/blob/17e6944daea8130e03903ccdfbf63f111c573849/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java
*/
private String cleanAddress(String hostname) {
// Clean out any anything after any zero bytes (this includes BungeeCord forwarding and the
// legacy Forge handshake indicator).
String cleaned = hostname;
int zeroIdx = cleaned.indexOf('\0');
if (zeroIdx > -1) {
cleaned = hostname.substring(0, zeroIdx);
}
// If we connect through an SRV record, there will be a period at the end (DNS usually elides
// this ending octet).
if (!cleaned.isEmpty() && cleaned.charAt(cleaned.length() - 1) == '.') {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
@@ -0,0 +1,155 @@
package net.tcpshield.tcpshield.velocity.handler;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
/**
* A player wrapper for Velocity
*/
public class VelocityPlayer implements PlayerProvider {
/*
* Reflection objects needed for manipulation
*/
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtil.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
REMOTE_ADDRESS_FIELD = ReflectionUtil.getPrivateField(minecraftConnection, "remoteAddress");
CLOSE_CHANNEL_METHOD = minecraftConnection.getMethod("close");
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
private final InboundConnection inboundConnection;
// private final boolean legacy;
private final ConnectionType connectionType;
private String ip;
public VelocityPlayer(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
// this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS && inboundConnection.getClass() != LOGIN_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
if (this.inboundConnection.getClass() == INITIAL_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.INITIAL_INBOUND;
} else if (this.inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.LOGIN_INBOUND;
} else {
this.connectionType = ConnectionType.LEGACY;
}
}
/**
* Unsupported with Velocity handshakes
*
* @return unknown
*/
@Override
public String getUUID() {
return "unknown";
}
/**
* Unsupported with Velocity handshakes
*
* @return unknown
*/
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object minecraftConnection = this.getMinecraftConnection();
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
// public boolean isLegacy() {
// return legacy;
// }
@Override
public void disconnect() {
try {
Object minecraftConnection = this.getMinecraftConnection();
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
private Object getMinecraftConnection() {
try {
switch (this.connectionType) {
case LEGACY:
return LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case INITIAL_INBOUND:
return MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case LOGIN_INBOUND: {
// starts as login_inbound, get delegate initial_inbound
Object initialInboundConnection = LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(this.inboundConnection);
return MINECRAFT_CONNECTION_FIELD.get(initialInboundConnection);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public ConnectionType getConnectionType() {
return connectionType;
}
enum ConnectionType {
LOGIN_INBOUND, INITIAL_INBOUND, LEGACY
}
}
+5 -3
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: 1.6
main: net.tcpshield.realip.TCPShieldBungee
author: 'https://tcpshield.com'
version: 2.7.0
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softdepends:
- floodgate
+14
View File
@@ -0,0 +1,14 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support = false
# enable handling handshake on PreLoginEvent
pre-login-event = true
+10 -1
View File
@@ -1,5 +1,14 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections: true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
debug-mode: false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support: false
# Spigot/Paper option only, does not affect bungeecord
prefer-protocollib: true
+8 -4
View File
@@ -1,5 +1,9 @@
name: TCPShield
version: 1.6
main: net.tcpshield.realip.TCPShieldBukkit
depend: ['ProtocolLib']
author: 'https://tcpshield.com'
version: 2.7.0
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
- floodgate
author: https://tcpshield.com
description: TCPShield IP parsing capabilities for Velocity
api-version: 1.16
+16
View File
@@ -0,0 +1,16 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.7.0",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [
{
"id": "floodgate",
"optional": true
}
],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}