29 Commits
Author SHA1 Message Date
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
112 changed files with 3094 additions and 2167 deletions

No files matched your search

+4 -1
View File
@@ -10,4 +10,7 @@ build/
.gradle/
gradle/
gradlew
gradlew.bat
gradlew.bat
libs/
+5 -5
View File
@@ -6,13 +6,12 @@ It also parses passed IP addresses so the server is aware of the real player IP
### Compatibility
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
This does not apply when using Paper 1.16 build #503 or higher.
### Setup
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/config/tcpshield-plugin) guidelines.
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/panel/tcpshield-plugin) guidelines.
### Compiling
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
@@ -23,12 +22,13 @@ gradle build
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
See [Contact](https://docs.tcpshield.com/about-us)
See [Contact](https://tcpshield.com/#contact)
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Dylan Keir](https://github.com/DylanKeir)
* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+90 -82
View File
@@ -1,4 +1,3 @@
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
@@ -6,87 +5,41 @@ 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'
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'
id 'fabric-loom' version '0.6-SNAPSHOT'
}
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://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
}
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '4.4.0'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.16.5-R0.1-20210227.144841-85'
// 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'
// Fabric
minecraft "com.mojang:minecraft:1.16.5"
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
modImplementation group: 'net.fabricmc', name: 'fabric-loader', version: '0.11.2'
// 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.geyser:floodgate-spigot:2.0'
}
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.5'
// --
// Variables
// --
version = '2.6.1'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
// --
// Misc.
// --
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
sourceSets {
main {
java {
@@ -106,24 +59,80 @@ sourceSets {
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
sourceCompatibility = "8"
targetCompatibility = "8"
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
// --
// 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/"
}
}
void updateYaml() {
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '5.0.0-SNAPSHOT'
// 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)
@@ -147,8 +156,7 @@ void updateYaml() {
}
}
void updateJson() {
def gson = new GsonBuilder().setPrettyPrinting().create();
void updateJsons() {
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
@@ -159,7 +167,7 @@ void updateJson() {
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(gson.toJson(object))
writer.write(object.toString())
}
}
}
@@ -167,4 +175,4 @@ void updateJson() {
}
}
build.dependsOn updateVersion
build.dependsOn updateVersion
Binary file not shown.
+2 -10
View File
@@ -1,10 +1,2 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'TCPShield'
@@ -1,93 +0,0 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.InvalidIPException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.IPValidation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
// raw payload is passed when it's not available -> e.g. Paper server list ping events
if (rawPayload == null) return;
String extraData = null;
// fix for e.g. incoming FML tagged packets
String cleanedPayload;
int nullIndex = rawPayload.indexOf('\0');
if (nullIndex != -1) { // FML tagged
cleanedPayload = rawPayload.substring(0, nullIndex);
extraData = rawPayload.substring(nullIndex);
} else { // standard
cleanedPayload = rawPayload;
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length < 2) return; // is nonetheless allowed as it passed the IP check
String hostname = payload[0];
String ipData = payload[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidIPException e) {
handleInvalidIPException(player, rawPayload);
} catch (IPModificationFailureException e) {
this.logger.warning(String.format("%s's IP failed to be modified. Raw payload = \"%s\"", player.getIP(), rawPayload));
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvalidIPException(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s was disconnected because the client failed the ip check and only-allowed-proxy-connections is enabled. Raw payload = \"%s\"", player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
public TCPShieldConfig getConfig() {
return config;
}
}
@@ -1,107 +0,0 @@
package net.tcpshield.tcpshield;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ReflectionUtils {
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;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
if (modifiersField == null) {
try {
modifiersField = getDeclaredField(Field.class, "modifiers");
} catch (NoSuchFieldException 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 TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
modifiersField.setAccessible(true);
}
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
setField(object, field, value);
}
public static void setField(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setField(Object object, Field field, Object value) throws IllegalAccessException {
field.set(object, value);
}
public static Object getObjectInPrivateField(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException {
Field field = getPrivateField(object.getClass(), fieldName);
return field.get(object);
}
public static Field getPrivateField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
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 IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
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 e;
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,205 @@
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.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);
}
}
}
@@ -1,9 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
public interface IPacket {
String getRawPayload();
void modifyOriginalPacket(String hostname) throws Exception;
}
@@ -1,15 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.net.InetSocketAddress;
public interface IPlayer {
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
void disconnect();
}
@@ -1,28 +0,0 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected File ipWhitelistFolder;
protected boolean debug;
protected boolean geyser;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
return this.debug;
}
public boolean isGeyser() {
return this.geyser;
}
}
@@ -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);
}
}
}
@@ -1,38 +1,75 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.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;
public class TCPShieldBukkit extends JavaPlugin {
/**
* The entry point for Bukkit servers
*/
public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
@Override
public void onEnable() {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper (1.16 build #503 or higher) to enable native compatibility or install ProtocolLib.");
return;
}
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
new TCPShieldProtocolLib(this).load();
}
}
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;
}
/**
* Checks if the server version supports native Paper support (Paper 1.16.5 build #503 or higher)
*
* @return if the server version supports native Paper support
*/
private boolean isPaper() {
try {
Class<?> handshakeEvent = Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
handshakeEvent.getMethod("getOriginalSocketAddressHostname");
return true;
} catch (ClassNotFoundException | NoSuchMethodException e) {
return false;
}
}
}
@@ -1,21 +0,0 @@
package net.tcpshield.tcpshield.bukkit.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -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());
}
}
@@ -1,43 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class PaperHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public PaperHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
IPacket packet = new PaperServerListPacketImpl(e);
IPlayer player = new PaperServerListPingPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -1,28 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserHandshakePacketHandler;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.velocity.VelocityHandshakePacketHandler;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldPaper {
private final JavaPlugin plugin;
public TCPShieldPaper(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(handshakePacketHandler);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
});
}
}
@@ -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);
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperPacketImpl implements IPacket {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPacketImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getRawPayload() {
return handshakeEvent.getOriginalHandshake();
}
@Override
public void modifyOriginalPacket(String hostname) {
handshakeEvent.setCancelled(false);
handshakeEvent.setServerHostname(hostname);
}
}
@@ -1,33 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.kyori.adventure.text.Component;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperPlayerImpl implements IPlayer {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayerImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getIP() {
return handshakeEvent.getOriginalSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.failMessage(Component.text("Connection failed. Please try again or contact an administrator."));
handshakeEvent.setFailed(true);
}
}
@@ -1,23 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperServerListPacketImpl implements IPacket {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPacketImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getRawPayload() {
return null;
}
@Override
public void modifyOriginalPacket(String hostname) {
pingEvent.setCancelled(false);
}
}
@@ -1,30 +0,0 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperServerListPingPlayerImpl implements IPlayer {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPingPlayerImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getIP() {
return pingEvent.getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
// not necessary since this class is only for server list pings
}
@Override
public void disconnect() {
pingEvent.setCancelled(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);
}
}
@@ -1,30 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
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.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPacketImpl;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPlayerImpl;
import org.bukkit.plugin.java.JavaPlugin;
public class ProtocolLibHandshakePacketHandler extends PacketAdapter {
private final HandshakePacketHandler handshakePacketHandler;
public ProtocolLibHandshakePacketHandler(JavaPlugin plugin, HandshakePacketHandler handshakePacketHandler) {
super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = handshakePacketHandler;
}
@Override
public void onPacketReceiving(PacketEvent e) {
IPacket packet = new ProtocolLibPacketImpl(e.getPacket());
IPlayer player = new ProtocolLibPlayerImpl(e.getPlayer());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
private final JavaPlugin plugin;
public TCPShieldProtocolLib(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin, handshakePacketHandler);
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("");
}
}
@@ -1,25 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.events.PacketContainer;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class ProtocolLibPacketImpl implements IPacket {
private final PacketContainer packetContainer;
private final String rawPayload;
public ProtocolLibPacketImpl(PacketContainer packetContainer) {
this.packetContainer = packetContainer;
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) {
packetContainer.getStrings().write(0, hostname);
}
}
@@ -1,64 +0,0 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
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 TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtils.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtils.getObjectInPrivateField(injector, "networkManager");
ReflectionUtils.setFinalField(networkManager, ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(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);
}
}
}
@@ -1,38 +0,0 @@
package net.tcpshield.tcpshield.bungee;
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.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bungee.impl.BungeePacketImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePlayerImpl;
public class BungeeHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public BungeeHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@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) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -3,19 +3,58 @@ 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.HandshakePacketHandler;
import net.tcpshield.tcpshield.bungee.impl.BungeeConfigImpl;
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;
public class TCPShieldBungee extends Plugin implements Listener {
/**
* The entry point for Bungee servers
*/
public class TCPShieldBungee extends Plugin implements TCPShieldPlugin, Listener {
@Override
public void onEnable() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.getLogger(), new BungeeConfigImpl(this));
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;
}
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(handshakePacketHandler));
});
}
}
@@ -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();
}
}
@@ -1,58 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
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.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class BungeeConfigImpl extends TCPShieldConfig {
private final Plugin plugin;
public BungeeConfigImpl(Plugin plugin) {
this.plugin = plugin;
saveDefaultConfig();
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
File dataFolder = plugin.getDataFolder();
if (!dataFolder.exists()) dataFolder.mkdir();
File file = getConfigFile();
if (file.exists()) return;
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
File dataFolder = plugin.getDataFolder();
return new File(dataFolder, "config.yml");
}
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);
}
}
}
@@ -1,36 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.packet.Handshake;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import java.net.InetSocketAddress;
public class BungeePacketImpl implements IPacket {
private final Handshake handshake;
private final PendingConnection pendingConnection;
public BungeePacketImpl(Handshake handshake, PendingConnection pendingConnection) {
this.handshake = handshake;
this.pendingConnection = pendingConnection;
}
@Override
public String getRawPayload() {
return handshake.getHost();
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
InetSocketAddress virtualHost = InetSocketAddress.createUnresolved(hostname, handshake.getPort());
try {
ReflectionUtils.setFinalField(pendingConnection, "virtualHost", virtualHost);
} catch (Exception ex) {
ReflectionUtils.setFinalField(pendingConnection, "vHost", virtualHost);
}
ReflectionUtils.setField(handshake, "host", hostname);
}
}
@@ -1,54 +0,0 @@
package net.tcpshield.tcpshield.bungee.impl;
import io.netty.channel.AbstractChannel;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class BungeePlayerImpl implements IPlayer {
private final PendingConnection pendingConnection;
private String ip;
public BungeePlayerImpl(PendingConnection pendingConnection) {
this.pendingConnection = pendingConnection;
this.ip = pendingConnection.getAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
try {
Field socketAddressField = ReflectionUtils.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtils.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
}
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
pendingConnection.disconnect();
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -1,4 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends SecurityException {
}
@@ -1,16 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldIPListUpdaterException extends RuntimeException {
public TCPShieldIPListUpdaterException(Throwable cause) {
super(cause);
}
public TCPShieldIPListUpdaterException(String message, Throwable cause) {
super(message, cause);
}
public TCPShieldIPListUpdaterException(String message) {
super(message);
}
}
@@ -1,12 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(String message) {
super(message);
}
}
@@ -1,18 +0,0 @@
package net.tcpshield.tcpshield.fabric;
import net.fabricmc.api.ModInitializer;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.fabric.impl.FabricConfigImpl;
import java.util.logging.Logger;
public class TCPShieldFabric implements ModInitializer {
public static final Logger LOGGER = Logger.getLogger("TCPShield");
public static final HandshakePacketHandler PACKET_HANDLER = new HandshakePacketHandler(LOGGER, new FabricConfigImpl());
@Override
public void onInitialize() {
LOGGER.info("TCPShield has been loaded.");
}
}
@@ -1,105 +0,0 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.fabricmc.loader.api.FabricLoader;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class FabricConfigImpl extends TCPShieldConfig {
private final Path configLocation = new File(FabricLoader.getInstance().getConfigDir().toString(), "tcpshield.yml").toPath();
public FabricConfigImpl() {
if(!configExists()) {
createDefaultConfig();
}
ConfigData config = loadConfig();
this.onlyProxy = config.onlyAllowProxyConnections;
this.ipWhitelistFolder = new File(FabricLoader.getInstance().getGameDirectory(), "ip-whitelist");
this.geyser = false;
this.debug = config.debugMode;
}
/**
* @return whether the file specified in {@link FabricConfigImpl#configLocation} exists.
*/
private boolean configExists() {
return Files.exists(configLocation);
}
/**
* Creates the default configuration file at the location specified in {@link FabricConfigImpl#configLocation}.
*
* <p>
* This operation will always override an existing configuration file if it exists.
* Callers should check {@link FabricConfigImpl#configExists()} if they wish to avoid this behavior.
*/
private void createDefaultConfig() {
// Ensure the parent directory of our config file exists.
// If it does not exist, attempt to create it now.
Path parentDirectory = configLocation.getParent();
if (!Files.exists(parentDirectory) || !Files.isDirectory(parentDirectory)) {
parentDirectory.toFile().mkdirs();
}
// Copy the config.yml data from our mod jar to the loader config folder.
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
Files.copy(in, configLocation);
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
private ConfigData loadConfig() {
try {
List<String> strings = Files.readAllLines(configLocation);
boolean onlyAllowProxyConnections = true;
boolean debugMode = false;
// Rudimentary config parsing
for (String line : strings) {
String[] keyValue = line.split(": ");
// A config option will only be valid if it is in the format 'a: b'.
// Ensure that is the case now.
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
switch (key) {
case "only-allow-proxy-connections":
onlyAllowProxyConnections = Boolean.parseBoolean(value);
break;
case "debug-mode":
debugMode = Boolean.parseBoolean(value);
break;
}
}
}
return new ConfigData(onlyAllowProxyConnections, debugMode);
} catch (Exception e) {
throw new TCPShieldInitializationException("Couldn't load config in config/tclshield.yml!");
}
}
private static class ConfigData {
protected boolean onlyAllowProxyConnections;
protected boolean debugMode;
public ConfigData(boolean onlyAllowProxyConnections, boolean debugMode) {
this.onlyAllowProxyConnections = onlyAllowProxyConnections;
this.debugMode = debugMode;
}
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.fabric.mixin.HandshakeC2SPacketAccessor;
public class FabricPacketImpl implements IPacket {
private final HandshakeC2SPacket handshake;
public FabricPacketImpl(HandshakeC2SPacket handshake) {
this.handshake = handshake;
}
@Override
public String getRawPayload() {
return ((HandshakeC2SPacketAccessor) handshake).getAddress();
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
// NO OPERATION
}
}
@@ -1,40 +0,0 @@
package net.tcpshield.tcpshield.fabric.impl;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginDisconnectS2CPacket;
import net.minecraft.text.LiteralText;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.fabric.mixin.ClientConnectionAccessor;
import java.net.InetSocketAddress;
public class FabricPlayerImpl implements IPlayer {
private final ClientConnection connection;
private String ip;
public FabricPlayerImpl(HandshakeC2SPacket packet, ClientConnection connection) {
this.connection = connection;
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
// At this point, the IP/connection believe the player has the IP of TCPShield.
// The ip passed into this method contains their CORRECT data, which we have to assign to the player network connection.
((ClientConnectionAccessor) connection).setAddress(ip);
this.ip = ((InetSocketAddress) ((ClientConnectionAccessor) connection).getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public void disconnect() {
connection.disconnect(new LiteralText("Connection failed. Please try again or contact an administrator."));
}
}
@@ -1,20 +0,0 @@
package net.tcpshield.tcpshield.fabric.mixin;
import io.netty.channel.Channel;
import net.minecraft.network.ClientConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.net.SocketAddress;
@Mixin(ClientConnection.class)
public interface ClientConnectionAccessor {
@Accessor
void setAddress(SocketAddress address);
@Accessor
Channel getChannel();
@Accessor
SocketAddress getAddress();
}
@@ -1,11 +0,0 @@
package net.tcpshield.tcpshield.fabric.mixin;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(HandshakeC2SPacket.class)
public interface HandshakeC2SPacketAccessor {
@Accessor
String getAddress();
}
@@ -1,32 +0,0 @@
package net.tcpshield.tcpshield.fabric.mixin;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
import net.minecraft.server.network.ServerHandshakeNetworkHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.fabric.TCPShieldFabric;
import net.tcpshield.tcpshield.fabric.impl.FabricPacketImpl;
import net.tcpshield.tcpshield.fabric.impl.FabricPlayerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerHandshakeNetworkHandler.class)
public class ServerHandshakeMixin {
@Shadow @Final private ClientConnection connection;
@Inject(
method = "onHandshake",
at = @At("HEAD"))
private void onHandshake(HandshakeC2SPacket handshake, CallbackInfo ci) {
IPacket packet = new FabricPacketImpl(handshake);
IPlayer player = new FabricPlayerImpl(handshake, connection);
TCPShieldFabric.PACKET_HANDLER.onHandshake(packet, player);
}
}
@@ -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);
});
}
}
@@ -1,29 +0,0 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.geyser.impl.GeyserPacketImpl;
import net.tcpshield.tcpshield.geyser.impl.GeyserPlayerImpl;
import org.geysermc.floodgate.api.InstanceHolder;
public class GeyserHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public GeyserHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
public void init() {
InstanceHolder.getHandshakeHandlers().addHandshakeHandler(data -> {
// In case the connection is a Bedrock connection, we don't bother
if (data.getBedrockData() != null) return;
IPlayer player = new GeyserPlayerImpl(data);
IPacket packet = new GeyserPacketImpl(data);
handshakePacketHandler.onHandshake(packet, player);
});
}
}
@@ -1,27 +1,33 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.util.UUID;
public class GeyserUtils {
public static void initGeyserOrDefault(HandshakePacketHandler handshakePacketHandler, Runnable registrar) {
if (!useGeyser(handshakePacketHandler.getConfig())) {
registrar.run();
} else {
GeyserHandshakePacketHandler geyserHandler = new GeyserHandshakePacketHandler(handshakePacketHandler);
geyserHandler.init();
}
}
public static final String SESSION_SECRET = UUID.randomUUID().toString();
public static boolean GEYSER_SUPPORT_ENABLED = false;
private static boolean useGeyser(TCPShieldConfig config) {
if (!config.isGeyser()) return 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;
}
}
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -1,23 +0,0 @@
package net.tcpshield.tcpshield.geyser.impl;
import net.tcpshield.tcpshield.abstraction.IPacket;
import org.geysermc.floodgate.api.handshake.HandshakeData;
public class GeyserPacketImpl implements IPacket {
private final HandshakeData handshakeData;
public GeyserPacketImpl(HandshakeData handshakeData) {
this.handshakeData = handshakeData;
}
@Override
public String getRawPayload() {
return handshakeData.getHostname();
}
@Override
public void modifyOriginalPacket(String hostname) {
handshakeData.setHostname(hostname);
}
}
@@ -1,30 +0,0 @@
package net.tcpshield.tcpshield.geyser.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import java.net.InetSocketAddress;
public class GeyserPlayerImpl implements IPlayer {
private final HandshakeData handshakeData;
public GeyserPlayerImpl(HandshakeData handshakeData) {
this.handshakeData = handshakeData;
}
@Override
public String getIP() {
return ((InetSocketAddress) handshakeData.getChannel().remoteAddress()).getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeData.setIp(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeData.getChannel().close();
}
}
@@ -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;
}
}
@@ -1,24 +0,0 @@
package net.tcpshield.tcpshield.validation;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class IPListAutoUpdater {
private final IPListUpdater ipListUpdater;
public IPListAutoUpdater(IPListUpdater ipListUpdater) {
this.ipListUpdater = ipListUpdater;
}
public void init() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
ipListUpdater.update();
}
}, TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(12));
}
}
@@ -1,80 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldIPListUpdaterException;
import net.tcpshield.tcpshield.validation.cidr.CIDRMatcher;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class IPListUpdater {
private final TCPShieldConfig config;
private final IPValidation ipValidation;
private final Logger logger;
public IPListUpdater(TCPShieldConfig config, IPValidation ipValidation, Logger logger) {
this.config = config;
this.ipValidation = ipValidation;
this.logger = logger;
}
public void update() {
try {
String ipList = getIPList();
File ipWhitelistFolder = config.getIPWhitelistFolder();
File tcpShieldIPList = new File(ipWhitelistFolder, "tcpshield-ips.list");
if (tcpShieldIPList.createNewFile()) {
logger.info("Fetched updated IP Whitelist from https://tcpshield.com/v4/");
}
try (FileWriter writer = new FileWriter(tcpShieldIPList, false)) {
writer.write(ipList);
}
ipValidation.readIPWhitelist();
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException(e);
}
}
public String getIPList() throws IOException {
URL url = new URL("https://tcpshield.com/v4/");
HttpsURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
int responseCode = connection.getResponseCode();
if (responseCode != 200)
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ did not return status code 200: " + responseCode);
String data = bufferedReader.lines()
.filter(str -> !str.isEmpty())
.peek(cidr -> { // checks for validity of each CIDR entry
try {
new CIDRMatcher(cidr);
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException("https://tcpshield.com/v4/ returned a faulty CIDR entry: " + cidr, e);
}
})
.collect(Collectors.joining("\n"));
if (data.isEmpty())
throw new TCPShieldIPListUpdaterException("Response from https://tcpshield.com/v4/ empty");
return data;
}
} finally {
if (connection != null)
connection.disconnect();
}
}
}
@@ -1,69 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.cidr.CIDRChecker;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.logging.Logger;
public class IPValidation {
private final TCPShieldConfig config;
private final IPListUpdater ipListUpdater;
private final CIDRChecker cidrChecker;
private List<String> ipWhitelist = new ArrayList<>();
public IPValidation(TCPShieldConfig config, Logger logger) {
this.config = config;
this.ipListUpdater = new IPListUpdater(config, this, logger);
new IPListAutoUpdater(ipListUpdater).init();
checkIPList();
try {
readIPWhitelist();
this.cidrChecker = new CIDRChecker(ipWhitelist);
} catch (IOException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean validateIP(InetAddress inetAddress) {
return cidrChecker.check(inetAddress);
}
private List<String> getIPWhitelist() throws IOException {
List<String> ipWhitelist = new ArrayList<>();
for (File file : Objects.requireNonNull(config.getIPWhitelistFolder().listFiles())) {
if (file.isDirectory()) continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
ipWhitelist.add(cidrEntry);
}
}
}
return ipWhitelist;
}
public void readIPWhitelist() throws IOException {
this.ipWhitelist = getIPWhitelist();
}
private void checkIPList() {
if (config.getIPWhitelistFolder().mkdirs()) {
ipListUpdater.update();
}
}
}
@@ -1,38 +0,0 @@
package net.tcpshield.tcpshield.validation.cidr;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CIDRChecker {
private final List<CIDRMatcher> cidrMatchers = new ArrayList<>();
private final Set<String> cache = new HashSet<>(); // quick cache for already verified IPs; HashSet is O(1) on HashSet#contains() operations
public CIDRChecker(List<String> cidrList) {
for (String cidr : cidrList) {
CIDRMatcher cidrMatcher = new CIDRMatcher(cidr);
cidrMatchers.add(cidrMatcher);
}
}
public boolean check(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip)) return true;
if (!checkUncached(inetAddress)) return false;
cache.add(ip);
return true;
}
private boolean checkUncached(InetAddress inetAddress) {
for (CIDRMatcher cidrMatcher : cidrMatchers) {
if (cidrMatcher.matches(inetAddress)) return true;
}
return false;
}
}
@@ -1,59 +0,0 @@
package net.tcpshield.tcpshield.validation.cidr;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CIDRMatcher {
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
public CIDRMatcher(String ipAddress) {
String[] split = ipAddress.split("/");
String parsedIPAddress;
if (split.length >= 2) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = ipAddress;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean matches(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;
}
}
@@ -6,34 +6,81 @@ 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.HandshakePacketHandler;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
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;
@Plugin(id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
/**
* The entry point for Velocity servers
*/
@Plugin(
id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity {
public class TCPShieldVelocity implements TCPShieldPlugin {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
private ConfigProvider configProvider;
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.logger, new VelocityConfigImpl(dataFolder.toFile()));
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> server.getEventManager().register(this, new VelocityHandshakePacketHandler(handshakePacketHandler)));
}
}
@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);
}
}
}
@@ -1,44 +0,0 @@
package net.tcpshield.tcpshield.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.velocity.impl.VelocityPacketImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPlayerImpl;
public class VelocityHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public VelocityHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
private void handleEvent(InboundConnection connection) {
VelocityPlayerImpl player = new VelocityPlayerImpl(connection);
if (player.isLegacy()) {
player.disconnect();
return;
}
IPacket packet = new VelocityPacketImpl(connection);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -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,109 @@
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");
HOSTNAME_FIELD = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
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
}
}
@@ -1,46 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class VelocityConfigImpl extends TCPShieldConfig {
private final File dataFolder;
private final File file;
public VelocityConfigImpl(File dataFolder) {
this.dataFolder = dataFolder;
this.file = new File(this.dataFolder, "config.toml");
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
this.geyser = toml.getBoolean("enable-geyser-compatibility");
this.debug = toml.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
if (!dataFolder.exists()) dataFolder.mkdir();
if (file.exists()) return;
try (InputStream in = VelocityConfigImpl.class.getResourceAsStream("/config.toml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
return new File(dataFolder, "config.toml");
}
private Toml loadConfig() {
return new Toml().read(getConfigFile());
}
}
@@ -1,72 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
public class VelocityPacketImpl implements IPacket {
private static final Field HANDSHAKE_FIELD;
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "handshake");
HOSTNAME_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
CLEANED_ADDRESS_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "cleanedAddress");
} catch (NoSuchFieldException | ClassNotFoundException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacketImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // pass on
}
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
ReflectionUtils.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
}
/**
* 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;
}
}
@@ -1,76 +0,0 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
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;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
REMOTE_ADDRESS_FIELD = ReflectionUtils.getPrivateField(minecraftConnection, "remoteAddress");
CLOSE_CHANNEL_METHOD = minecraftConnection.getMethod("close");
} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final boolean legacy;
private String ip;
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
public boolean isLegacy() {
return legacy;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
try {
Object minecraftConnection = legacy ? LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection) : MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new RuntimeException(e); // pass exception on
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
name: TCPShield
version: '2.5'
version: 2.6.1
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softDepends:
softdepends:
- floodgate
+9 -3
View File
@@ -1,8 +1,14 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# Only set this to true in case you're using TCPShield's Geyser proxy!
enable-geyser-compatibility = false
# 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
# enable handling handshake on PreLoginEvent
pre-login-event = true
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more